Files
blog.pyaqa.ru/tests/unit/application/test_publish_post.py
Sergey Vanyushkin ce2c052684
Some checks failed
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/type Pipeline failed
feat(tests): increase test coverage from 68% to 78%
Add comprehensive integration and API tests:
- Integration tests for SQLAlchemyPostRepository (34 tests)
- API tests for posts endpoints and error handlers (22 tests)
- Unit tests for PublishPostUseCase and ListPostsUseCase
- Unit tests for SessionTransactionManager

Also register generic exception handler in error_handler.py

All 167 tests pass, coverage now meets CI threshold of 70%
2026-05-02 18:40:29 +03:00

175 lines
5.6 KiB
Python

"""Tests for PublishPostUseCase.
Tests publishing and unpublishing posts with authorization.
"""
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
import pytest
from app.application.dtos import PostResponseDTO
from app.application.interfaces import TransactionManager
from app.application.use_cases.publish_post import PublishPostUseCase
from app.domain.entities import Post
from app.domain.exceptions import ForbiddenException, NotFoundException
from app.domain.repositories import PostRepository
from app.domain.value_objects import Content, Slug, Title
@pytest.fixture
def mock_post_repository() -> MagicMock:
"""Create mock post repository."""
return MagicMock(spec=PostRepository)
@pytest.fixture
def mock_transaction_manager() -> MagicMock:
"""Create mock transaction manager."""
tx = MagicMock(spec=TransactionManager)
tx.commit = AsyncMock()
return tx
@pytest.fixture
def publish_use_case(
mock_post_repository: MagicMock,
mock_transaction_manager: MagicMock,
) -> PublishPostUseCase:
"""Create publish use case with mocked dependencies."""
return PublishPostUseCase(mock_post_repository, mock_transaction_manager)
@pytest.fixture
def sample_post() -> Post:
"""Create a sample unpublished post."""
return Post(
id=uuid4(),
title=Title("Test Post"),
content=Content("Test content"),
slug=Slug("test-post"),
author_id="author-123",
published=False,
tags=["test"],
created_at=datetime.now(),
updated_at=datetime.now(),
)
@pytest.fixture
def published_post() -> Post:
"""Create a sample published post."""
post = Post(
id=uuid4(),
title=Title("Published Post"),
content=Content("Published content"),
slug=Slug("published-post"),
author_id="author-123",
published=True,
tags=["published"],
created_at=datetime.now(),
updated_at=datetime.now(),
)
return post
class TestPublishPost:
"""Test suite for publish method."""
async def test_publish_success(
self,
publish_use_case: PublishPostUseCase,
mock_post_repository: MagicMock,
mock_transaction_manager: MagicMock,
sample_post: Post,
) -> None:
"""Test successful post publishing."""
mock_post_repository.get_by_id = AsyncMock(return_value=sample_post)
mock_post_repository.update = AsyncMock()
result = await publish_use_case.publish(sample_post.id, sample_post.author_id)
assert isinstance(result, PostResponseDTO)
assert result.id == sample_post.id
assert result.published is True
mock_post_repository.update.assert_called_once()
mock_transaction_manager.commit.assert_called_once()
async def test_publish_not_found(
self,
publish_use_case: PublishPostUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test publishing non-existent post raises NotFoundException."""
mock_post_repository.get_by_id = AsyncMock(return_value=None)
with pytest.raises(NotFoundException) as exc_info:
await publish_use_case.publish(uuid4(), "author-123")
assert "not found" in str(exc_info.value).lower()
async def test_publish_forbidden(
self,
publish_use_case: PublishPostUseCase,
mock_post_repository: MagicMock,
sample_post: Post,
) -> None:
"""Test publishing other user's post raises ForbiddenException."""
mock_post_repository.get_by_id = AsyncMock(return_value=sample_post)
with pytest.raises(ForbiddenException) as exc_info:
await publish_use_case.publish(sample_post.id, "different-author")
assert "own posts" in str(exc_info.value).lower()
class TestUnpublishPost:
"""Test suite for unpublish method."""
async def test_unpublish_success(
self,
publish_use_case: PublishPostUseCase,
mock_post_repository: MagicMock,
mock_transaction_manager: MagicMock,
published_post: Post,
) -> None:
"""Test successful post unpublishing."""
mock_post_repository.get_by_id = AsyncMock(return_value=published_post)
mock_post_repository.update = AsyncMock()
result = await publish_use_case.unpublish(published_post.id, published_post.author_id)
assert isinstance(result, PostResponseDTO)
assert result.id == published_post.id
assert result.published is False
mock_post_repository.update.assert_called_once()
mock_transaction_manager.commit.assert_called_once()
async def test_unpublish_not_found(
self,
publish_use_case: PublishPostUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test unpublishing non-existent post raises NotFoundException."""
mock_post_repository.get_by_id = AsyncMock(return_value=None)
with pytest.raises(NotFoundException) as exc_info:
await publish_use_case.unpublish(uuid4(), "author-123")
assert "not found" in str(exc_info.value).lower()
async def test_unpublish_forbidden(
self,
publish_use_case: PublishPostUseCase,
mock_post_repository: MagicMock,
published_post: Post,
) -> None:
"""Test unpublishing other user's post raises ForbiddenException."""
mock_post_repository.get_by_id = AsyncMock(return_value=published_post)
with pytest.raises(ForbiddenException) as exc_info:
await publish_use_case.unpublish(published_post.id, "different-author")
assert "own posts" in str(exc_info.value).lower()