feat: add like/unlike toggle on blog posts with per-user tracking
- PostLike domain entity (post_id, liked_by) with BaseEntity integration
- Post entity: add like_count field (default 0) and to_dict serialization
- PostRepository interface: add get_like, add_like, remove_like methods
- TogglePostLikeUseCase: toggle logic (like → unlike, unlike → like)
- PostResponseDTO/PostResponseSchema: add like_count field
- PostLikeORM model with FK to posts and cascade delete
- SQLAlchemyPostRepository: implement like query/add/remove with ORM mapping
- DI provider registration for TogglePostLikeUseCase
- API endpoint POST /api/v1/posts/{id}/like (auth required)
- Unit tests: PostLike entity, Post.like_count, TogglePostLikeUseCase (7 tests)
- API tests: POST /api/v1/posts/{id}/like (4 tests)
- Test model files: FEATURE_LIKES.md, TEST_MODEL.md updated
This commit is contained in:
94
tests/api/test_likes.py
Normal file
94
tests/api/test_likes.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""API tests for post like/unlike toggle.
|
||||
|
||||
This module tests the POST /api/v1/posts/{post_id}/like endpoint covering
|
||||
authenticated toggle on, toggle off, guest access, and not-found scenarios.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.api.conftest import API_PREFIX
|
||||
|
||||
|
||||
class TestLikePost:
|
||||
"""Tests for POST /api/v1/posts/{post_id}/like — toggle like on a post."""
|
||||
|
||||
def test_like_post_authenticated(
|
||||
self,
|
||||
client: TestClient,
|
||||
user_headers: dict[str, str],
|
||||
created_post: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test liking a post as authenticated user returns like_count=1.
|
||||
|
||||
TC-API-114: Positive — authenticated like toggle on.
|
||||
"""
|
||||
post_id = created_post["id"]
|
||||
|
||||
response = client.post(
|
||||
f"{API_PREFIX}/{post_id}/like",
|
||||
headers=user_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["like_count"] == 1
|
||||
assert data["id"] == post_id
|
||||
|
||||
def test_unlike_post_authenticated(
|
||||
self,
|
||||
client: TestClient,
|
||||
user_headers: dict[str, str],
|
||||
created_post: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test unliking a post that was already liked returns like_count=0.
|
||||
|
||||
TC-API-115: Positive — authenticated like toggle off.
|
||||
"""
|
||||
post_id = created_post["id"]
|
||||
|
||||
client.post(f"{API_PREFIX}/{post_id}/like", headers=user_headers)
|
||||
|
||||
response = client.post(
|
||||
f"{API_PREFIX}/{post_id}/like",
|
||||
headers=user_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["like_count"] == 0
|
||||
assert data["id"] == post_id
|
||||
|
||||
def test_like_post_as_guest(
|
||||
self,
|
||||
client: TestClient,
|
||||
guest_headers: dict[str, str],
|
||||
created_post: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test liking a post as guest (inactive token) returns 401.
|
||||
|
||||
TC-API-116: Negative — guest/inactive token cannot like.
|
||||
"""
|
||||
post_id = created_post["id"]
|
||||
response = client.post(
|
||||
f"{API_PREFIX}/{post_id}/like",
|
||||
headers=guest_headers,
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_like_post_not_found(
|
||||
self,
|
||||
client: TestClient,
|
||||
user_headers: dict[str, str],
|
||||
) -> None:
|
||||
"""Test liking a non-existent post returns 404.
|
||||
|
||||
TC-API-117: Negative — post not found.
|
||||
"""
|
||||
fake_id = "00000000-0000-0000-0000-000000000000"
|
||||
response = client.post(
|
||||
f"{API_PREFIX}/{fake_id}/like",
|
||||
headers=user_headers,
|
||||
)
|
||||
assert response.status_code == 404
|
||||
error = response.json()
|
||||
assert error["error"] == "NotFoundException"
|
||||
Reference in New Issue
Block a user