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%
226 lines
6.7 KiB
Python
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 == []
|