"""Integration tests for SQLAlchemyPostRepository. Tests repository implementation with real in-memory SQLite database. Note: Some tests involving JSON array operations are skipped for SQLite as it has limited support compared to PostgreSQL. """ from uuid import UUID, uuid4 import pytest from sqlalchemy.ext.asyncio import AsyncSession from app.domain.entities import Post from app.domain.repositories import PostRepository from app.domain.value_objects import Content, Slug, Title from app.infrastructure.repositories.post import SQLAlchemyPostRepository @pytest.fixture def repository(db_session: AsyncSession) -> PostRepository: """Create repository instance for testing.""" return SQLAlchemyPostRepository(db_session) @pytest.fixture def sample_post() -> Post: """Create a sample post for testing.""" return Post( id=uuid4(), title=Title("Test Post Title"), content=Content("Test content for the blog post"), slug=Slug("test-post-title"), author_id="test-author-123", published=False, tags=["python", "testing"], ) @pytest.fixture def published_post() -> Post: """Create a published post for testing.""" post = Post( id=uuid4(), title=Title("Published Post"), content=Content("This is a published post content"), slug=Slug("published-post"), author_id="test-author-456", published=True, tags=["published", "blog"], ) return post class TestPostRepositoryCreate: """Test suite for post creation operations.""" async def test_add_post( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test adding a new post to the database.""" await repository.add(sample_post) await db_session.commit() retrieved = await repository.get_by_id(sample_post.id) assert retrieved is not None assert retrieved.id == sample_post.id assert retrieved.title.value == sample_post.title.value assert retrieved.content.value == sample_post.content.value assert retrieved.slug.value == sample_post.slug.value assert retrieved.author_id == sample_post.author_id assert retrieved.published == sample_post.published assert retrieved.tags == sample_post.tags async def test_get_by_id_existing( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test retrieving an existing post by ID.""" await repository.add(sample_post) await db_session.commit() result = await repository.get_by_id(sample_post.id) assert result is not None assert result.id == sample_post.id async def test_get_by_id_non_existing(self, repository: PostRepository) -> None: """Test retrieving a non-existing post returns None.""" non_existing_id = uuid4() result = await repository.get_by_id(non_existing_id) assert result is None class TestPostRepositoryGetAll: """Test suite for retrieving all posts.""" async def test_get_all_empty(self, repository: PostRepository) -> None: """Test retrieving all posts when database is empty.""" results = await repository.get_all() assert results == [] async def test_get_all_multiple_posts( self, repository: PostRepository, sample_post: Post, published_post: Post, db_session: AsyncSession, ) -> None: """Test retrieving all posts returns all entries.""" await repository.add(sample_post) await repository.add(published_post) await db_session.commit() results = await repository.get_all() assert len(results) == 2 ids = {post.id for post in results} assert sample_post.id in ids assert published_post.id in ids class TestPostRepositoryUpdate: """Test suite for post update operations.""" async def test_update_post( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test updating an existing post.""" await repository.add(sample_post) await db_session.commit() # Refresh to get latest state await db_session.flush() # Create a new post instance with updated values updated_post = Post( id=sample_post.id, title=Title("Updated Title"), content=Content("Updated content for the post"), slug=sample_post.slug, author_id=sample_post.author_id, published=sample_post.published, tags=["updated", "tags"], created_at=sample_post.created_at, updated_at=sample_post.updated_at, ) await repository.update(updated_post) await db_session.commit() retrieved = await repository.get_by_id(sample_post.id) assert retrieved is not None assert retrieved.title.value == "Updated Title" assert retrieved.content.value == "Updated content for the post" assert retrieved.tags == ["updated", "tags"] async def test_update_publishes_post( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test that update reflects published status change.""" await repository.add(sample_post) await db_session.commit() await db_session.flush() # Create updated post with published=True updated_post = Post( id=sample_post.id, title=sample_post.title, content=sample_post.content, slug=sample_post.slug, author_id=sample_post.author_id, published=True, tags=sample_post.tags, created_at=sample_post.created_at, updated_at=sample_post.updated_at, ) await repository.update(updated_post) await db_session.commit() retrieved = await repository.get_by_id(sample_post.id) assert retrieved is not None assert retrieved.published is True class TestPostRepositoryDelete: """Test suite for post deletion operations.""" async def test_delete_existing_post( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test deleting an existing post.""" await repository.add(sample_post) await db_session.commit() await repository.delete(sample_post.id) await db_session.commit() retrieved = await repository.get_by_id(sample_post.id) assert retrieved is None async def test_delete_non_existing_post(self, repository: PostRepository) -> None: """Test deleting a non-existing post does not raise error.""" non_existing_id = uuid4() await repository.delete(non_existing_id) class TestPostRepositoryExists: """Test suite for post existence checks.""" async def test_exists_true( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test exists returns True for existing post.""" await repository.add(sample_post) await db_session.commit() result = await repository.exists(sample_post.id) assert result is True async def test_exists_false(self, repository: PostRepository) -> None: """Test exists returns False for non-existing post.""" non_existing_id = uuid4() result = await repository.exists(non_existing_id) assert result is False class TestPostRepositoryGetBySlug: """Test suite for slug-based retrieval.""" async def test_get_by_slug_existing( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test retrieving post by existing slug.""" await repository.add(sample_post) await db_session.commit() result = await repository.get_by_slug(sample_post.slug.value) assert result is not None assert result.id == sample_post.id assert result.slug.value == sample_post.slug.value async def test_get_by_slug_non_existing(self, repository: PostRepository) -> None: """Test retrieving by non-existing slug returns None.""" result = await repository.get_by_slug("non-existing-slug") assert result is None class TestPostRepositoryGetByAuthor: """Test suite for author-based retrieval.""" async def test_get_by_author( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test retrieving posts by author ID.""" await repository.add(sample_post) await db_session.commit() results = await repository.get_by_author(sample_post.author_id) assert len(results) == 1 assert results[0].id == sample_post.id async def test_get_by_author_empty(self, repository: PostRepository) -> None: """Test retrieving posts by author with no posts.""" results = await repository.get_by_author("non-existing-author") assert results == [] class TestPostRepositoryGetPublished: """Test suite for published posts retrieval.""" async def test_get_published_only( self, repository: PostRepository, sample_post: Post, published_post: Post, db_session: AsyncSession, ) -> None: """Test retrieving only published posts.""" await repository.add(sample_post) await repository.add(published_post) await db_session.commit() results = await repository.get_published() assert len(results) == 1 assert results[0].id == published_post.id class TestPostRepositoryGetByTag: """Test suite for tag-based retrieval. Note: These tests are skipped for SQLite as it has limited JSON support. """ @pytest.mark.skip(reason="SQLite has limited JSON array support") async def test_get_by_tag(self, repository: PostRepository, sample_post: Post) -> None: """Test retrieving posts by tag.""" pass @pytest.mark.skip(reason="SQLite has limited JSON array support") async def test_get_by_tag_multiple_posts(self, repository: PostRepository) -> None: """Test retrieving multiple posts with same tag.""" pass @pytest.mark.skip(reason="SQLite has limited JSON array support") async def test_get_by_tag_not_found( self, repository: PostRepository, sample_post: Post, ) -> None: """Test retrieving by non-existing tag returns empty list.""" pass class TestPostRepositorySlugExists: """Test suite for slug existence checks.""" async def test_slug_exists_true( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test slug_exists returns True for existing slug.""" await repository.add(sample_post) await db_session.commit() result = await repository.slug_exists(sample_post.slug.value) assert result is True async def test_slug_exists_false(self, repository: PostRepository) -> None: """Test slug_exists returns False for non-existing slug.""" result = await repository.slug_exists("non-existing-slug") assert result is False class TestPostRepositorySearch: """Test suite for post search functionality.""" async def test_search_by_title( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test searching posts by title.""" await repository.add(sample_post) await db_session.commit() results = await repository.search("Test Post") assert len(results) == 1 assert results[0].id == sample_post.id async def test_search_by_content( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test searching posts by content.""" await repository.add(sample_post) await db_session.commit() results = await repository.search("blog post") assert len(results) == 1 assert results[0].id == sample_post.id async def test_search_case_insensitive( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test search is case insensitive.""" await repository.add(sample_post) await db_session.commit() results = await repository.search("TEST POST") assert len(results) == 1 async def test_search_no_results( self, repository: PostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test search with no matching results.""" await repository.add(sample_post) await db_session.commit() results = await repository.search("xyz123nonexistent") assert results == [] @pytest.mark.skip(reason="SQLite behavior without ORDER BY is non-deterministic") async def test_search_with_limit( self, repository: PostRepository, db_session: AsyncSession, ) -> None: """Test search with limit - skipped for SQLite.""" pass @pytest.mark.skip(reason="SQLite order non-deterministic without ORDER BY") async def test_search_with_offset( self, repository: PostRepository, db_session: AsyncSession, ) -> None: """Test search with offset.""" pass class TestPostRepositoryConversion: """Test suite for domain/ORM conversion.""" async def test_to_domain_preserves_all_fields( self, repository: SQLAlchemyPostRepository, sample_post: Post, db_session: AsyncSession, ) -> None: """Test that domain conversion preserves all post fields.""" await repository.add(sample_post) await db_session.commit() retrieved = await repository.get_by_id(sample_post.id) assert retrieved is not None assert isinstance(retrieved.id, UUID) assert retrieved.title.value == sample_post.title.value assert retrieved.content.value == sample_post.content.value assert retrieved.slug.value == sample_post.slug.value assert retrieved.author_id == sample_post.author_id assert retrieved.published == sample_post.published assert retrieved.tags == sample_post.tags