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%
This commit is contained in:
225
tests/unit/application/test_list_posts.py
Normal file
225
tests/unit/application/test_list_posts.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""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 == []
|
||||
Reference in New Issue
Block a user