Files
blog.pyaqa.ru/tests/unit/application/test_list_posts.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

226 lines
6.7 KiB
Python

"""Tests for ListPostsUseCase.
Tests listing posts with various filters.
"""
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.list_posts import ListPostsUseCase
from app.domain.entities import Post
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."""
return MagicMock(spec=TransactionManager)
@pytest.fixture
def list_use_case(
mock_post_repository: MagicMock,
mock_transaction_manager: MagicMock,
) -> ListPostsUseCase:
"""Create list use case with mocked dependencies."""
return ListPostsUseCase(mock_post_repository, mock_transaction_manager)
@pytest.fixture
def sample_posts() -> list[Post]:
"""Create sample posts for testing."""
return [
Post(
id=uuid4(),
title=Title(f"Post {i}"),
content=Content(f"Content for post number {i}"),
slug=Slug(f"post-{i}"),
author_id="author-123",
published=i % 2 == 0,
tags=["python"] if i == 0 else [],
created_at=datetime.now(),
updated_at=datetime.now(),
)
for i in range(3)
]
class TestAllPosts:
"""Test suite for all_posts method."""
async def test_all_posts(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
sample_posts: list[Post],
) -> None:
"""Test getting all posts."""
mock_post_repository.get_all = AsyncMock(return_value=sample_posts)
result = await list_use_case.all_posts()
assert len(result) == 3
assert all(isinstance(dto, PostResponseDTO) for dto in result)
mock_post_repository.get_all.assert_called_once()
async def test_all_posts_empty(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test getting all posts when empty."""
mock_post_repository.get_all = AsyncMock(return_value=[])
result = await list_use_case.all_posts()
assert result == []
class TestPublishedPosts:
"""Test suite for published_posts method."""
async def test_published_posts(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
sample_posts: list[Post],
) -> None:
"""Test getting published posts."""
published = [p for p in sample_posts if p.published]
mock_post_repository.get_published = AsyncMock(return_value=published)
result = await list_use_case.published_posts()
assert len(result) == 2 # posts 0 and 2 are published
assert all(dto.published for dto in result)
async def test_published_posts_with_limit_offset(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test getting published posts with pagination."""
mock_post_repository.get_published = AsyncMock(return_value=[])
result = await list_use_case.published_posts(limit=5, offset=10)
mock_post_repository.get_published.assert_called_once_with(limit=5, offset=10)
assert result == []
class TestByAuthor:
"""Test suite for by_author method."""
async def test_by_author(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
sample_posts: list[Post],
) -> None:
"""Test getting posts by author."""
mock_post_repository.get_by_author = AsyncMock(return_value=sample_posts)
result = await list_use_case.by_author("author-123")
assert len(result) == 3
mock_post_repository.get_by_author.assert_called_once_with(
"author-123", limit=None, offset=None
)
async def test_by_author_with_pagination(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test getting posts by author with pagination."""
mock_post_repository.get_by_author = AsyncMock(return_value=[])
await list_use_case.by_author("author-123", limit=5, offset=0)
mock_post_repository.get_by_author.assert_called_once_with("author-123", limit=5, offset=0)
class TestByTag:
"""Test suite for by_tag method."""
async def test_by_tag(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
sample_posts: list[Post],
) -> None:
"""Test getting posts by tag."""
tagged_posts = [sample_posts[0]]
mock_post_repository.get_by_tag = AsyncMock(return_value=tagged_posts)
result = await list_use_case.by_tag("python")
assert len(result) == 1
assert "python" in result[0].tags
async def test_by_tag_empty(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test getting posts by non-existent tag."""
mock_post_repository.get_by_tag = AsyncMock(return_value=[])
result = await list_use_case.by_tag("nonexistent")
assert result == []
class TestSearch:
"""Test suite for search method."""
async def test_search(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
sample_posts: list[Post],
) -> None:
"""Test searching posts."""
mock_post_repository.search = AsyncMock(return_value=sample_posts)
result = await list_use_case.search("test query")
assert len(result) == 3
mock_post_repository.search.assert_called_once_with("test query", limit=None, offset=None)
async def test_search_with_pagination(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test searching posts with pagination."""
mock_post_repository.search = AsyncMock(return_value=[])
await list_use_case.search("query", limit=10, offset=5)
mock_post_repository.search.assert_called_once_with("query", limit=10, offset=5)
async def test_search_no_results(
self,
list_use_case: ListPostsUseCase,
mock_post_repository: MagicMock,
) -> None:
"""Test searching with no matches."""
mock_post_repository.search = AsyncMock(return_value=[])
result = await list_use_case.search("xyz123")
assert result == []