"""Tests for TogglePostLikeUseCase. This module tests the like/unlike toggle use case covering first-time like, unlike, post-not-found, guest access, and identity isolation scenarios. """ from unittest.mock import AsyncMock, Mock from uuid import uuid4 import pytest from app.application.use_cases.toggle_like import TogglePostLikeUseCase from app.domain.entities import Post from app.domain.entities.like import PostLike from app.domain.exceptions import NotFoundException @pytest.fixture def test_post() -> Post: """Create a test post for like tests.""" return Post.create( title_str="Likeable Post", content_str="This post will be liked and unliked. Enough length here.", author_id="user-123", tags=["test"], ) class TestTogglePostLikeUseCase: """Tests for TogglePostLikeUseCase. Covers TC-UNIT-822 through TC-UNIT-825 and TC-UNIT-828. """ @pytest.mark.asyncio async def test_like_post_first_time( self, mock_post_repository: Mock, mock_transaction_manager: Mock, test_post: Post, ) -> None: """Test toggling like on a post for the first time. TC-UNIT-822: Positive — like first time. """ mock_post_repository.get_by_id = AsyncMock(return_value=test_post) mock_post_repository.get_like = AsyncMock(return_value=None) mock_post_repository.add_like = AsyncMock() mock_post_repository.remove_like = AsyncMock() mock_post_repository.update = AsyncMock() use_case = TogglePostLikeUseCase(mock_post_repository, mock_transaction_manager) result = await use_case.execute(test_post.id, "user-123") assert result.like_count == 1 mock_post_repository.add_like.assert_called_once() mock_post_repository.remove_like.assert_not_called() mock_post_repository.update.assert_called_once() mock_transaction_manager.commit.assert_called_once() @pytest.mark.asyncio async def test_unlike_post_already_liked( self, mock_post_repository: Mock, mock_transaction_manager: Mock, test_post: Post, ) -> None: """Test toggling like on a post that is already liked. TC-UNIT-823: Positive — unlike (already liked). """ existing_like = PostLike(post_id=test_post.id, liked_by="user-123") mock_post_repository.get_by_id = AsyncMock(return_value=test_post) mock_post_repository.get_like = AsyncMock(return_value=existing_like) mock_post_repository.add_like = AsyncMock() mock_post_repository.remove_like = AsyncMock() mock_post_repository.update = AsyncMock() use_case = TogglePostLikeUseCase(mock_post_repository, mock_transaction_manager) result = await use_case.execute(test_post.id, "user-123") assert result.like_count == 0 mock_post_repository.remove_like.assert_called_once() mock_post_repository.add_like.assert_not_called() mock_post_repository.update.assert_called_once() mock_transaction_manager.commit.assert_called_once() @pytest.mark.asyncio async def test_like_post_not_found( self, mock_post_repository: Mock, mock_transaction_manager: Mock, ) -> None: """Test toggling like on a non-existent post. TC-UNIT-824: Negative — post not found. """ mock_post_repository.get_by_id = AsyncMock(return_value=None) use_case = TogglePostLikeUseCase(mock_post_repository, mock_transaction_manager) with pytest.raises(NotFoundException): await use_case.execute(uuid4(), "user-123") mock_post_repository.add_like.assert_not_called() mock_post_repository.remove_like.assert_not_called() mock_transaction_manager.commit.assert_not_called() @pytest.mark.asyncio async def test_like_as_guest_with_device_id( self, mock_post_repository: Mock, mock_transaction_manager: Mock, test_post: Post, ) -> None: """Test toggling like as a guest using device_id. TC-UNIT-825: Positive — guest via device_id. """ device_id = "device-abc-123" mock_post_repository.get_by_id = AsyncMock(return_value=test_post) mock_post_repository.get_like = AsyncMock(return_value=None) mock_post_repository.add_like = AsyncMock() mock_post_repository.remove_like = AsyncMock() mock_post_repository.update = AsyncMock() use_case = TogglePostLikeUseCase(mock_post_repository, mock_transaction_manager) result = await use_case.execute(test_post.id, device_id) assert result.like_count == 1 added_like = mock_post_repository.add_like.call_args[0][0] assert added_like.liked_by == device_id assert added_like.post_id == test_post.id @pytest.mark.asyncio async def test_two_users_can_both_like( self, mock_post_repository: Mock, mock_transaction_manager: Mock, test_post: Post, ) -> None: """Test that two different users can both like the same post. TC-UNIT-828: Positive — identity isolation. Both likes are counted independently. """ mock_post_repository.get_by_id = AsyncMock(return_value=test_post) mock_post_repository.get_like = AsyncMock(return_value=None) mock_post_repository.add_like = AsyncMock() mock_post_repository.remove_like = AsyncMock() mock_post_repository.update = AsyncMock() use_case = TogglePostLikeUseCase(mock_post_repository, mock_transaction_manager) result1 = await use_case.execute(test_post.id, "user-123") assert result1.like_count == 1 mock_post_repository.add_like.reset_mock() mock_post_repository.update.reset_mock() mock_transaction_manager.commit.reset_mock() result2 = await use_case.execute(test_post.id, "user-456") assert result2.like_count == 2 assert mock_post_repository.add_like.call_count == 1