diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index 760d30aa8..8a080ee84 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -117,80 +117,80 @@ def like_blog_post( db: Session = Depends(get_db), current_user: User = Depends(user_service.get_current_user), ): + """Endpoint to add `like` to a blog post. + args: + blog_id: `str` The ID of the blog post. + request: `default` Request. + db: `default` Session. + + return: + In the `data` returned, `"object"` represents details of the + BlogLike obj and the `"objects_count"` represents the number + of BlogLike for the blog post + """ blog_service = BlogService(db) - # GET blog post + # get blog post blog_p = blog_service.fetch(blog_id) - if not isinstance(blog_p, Blog): - raise HTTPException( - detail="Post not found", status_code=status.HTTP_404_NOT_FOUND - ) - - # CONFIRM current user has NOT liked before - existing_like = blog_service.fetch_blog_like(blog_p.id, current_user.id) - if isinstance(existing_like, BlogLike): - raise HTTPException( - detail="You have already liked this blog post", - status_code=status.HTTP_403_FORBIDDEN, - ) - - # UPDATE likes - blog_service.create_blog_like( + + # confirm current user has NOT liked before + blog_service.check_user_already_liked_blog(blog_p, current_user) + + # update likes + new_like = blog_service.create_blog_like( db, blog_p.id, current_user.id, ip_address=get_ip_address(request)) - - # CONFIRM new like - new_like = blog_service.fetch_blog_like(blog_p.id, current_user.id) - if not isinstance(new_like, BlogLike): - raise HTTPException( - detail="Unable to record like.", status_code=status.HTTP_400_BAD_REQUEST - ) # Return success response return success_response( status_code=status.HTTP_200_OK, message="Like recorded successfully.", - data=new_like.to_dict(), + data={ + 'object': new_like.to_dict(), + 'objects_count': blog_service.num_of_likes(blog_id) + }, ) -@blog.put("/{blog_id}/dislike", response_model=BlogLikeDislikeResponse) +@blog.post("/{blog_id}/dislike", response_model=BlogLikeDislikeResponse) def dislike_blog_post( blog_id: str, + request: Request, db: Session = Depends(get_db), current_user: User = Depends(user_service.get_current_user), ): + """Endpoint to add `dislike` to a blog post. + args: + blog_id: `str` The ID of the blog post. + request: `default` Request. + db: `default` Session. + + return: + In the `data` returned, `"object"` represents details of the + BlogDislike obj and the `"objects_count"` represents the number + of BlogDislike for the blog post + """ blog_service = BlogService(db) - # GET blog post + # get blog post blog_p = blog_service.fetch(blog_id) - if not isinstance(blog_p, Blog): - raise HTTPException( - detail="Post not found", status_code=status.HTTP_404_NOT_FOUND - ) - - # CONFIRM current user has NOT disliked before - existing_dislike = blog_service.fetch_blog_dislike(blog_p.id, current_user.id) - if isinstance(existing_dislike, BlogDislike): - raise HTTPException( - detail="You have already disliked this blog post", - status_code=status.HTTP_403_FORBIDDEN, - ) - - # UPDATE disikes - new_dislike = blog_service.create_blog_dislike(db, blog_p.id, current_user.id) - - if not isinstance(new_dislike, BlogDislike): - raise HTTPException( - detail="Unable to record dislike.", status_code=status.HTTP_400_BAD_REQUEST - ) + + # confirm current user has NOT disliked before + blog_service.check_user_already_disliked_blog(blog_p, current_user) + + # update disikes + new_dislike = blog_service.create_blog_dislike( + db, blog_p.id, current_user.id, ip_address=get_ip_address(request)) # Return success response return success_response( status_code=status.HTTP_200_OK, message="Dislike recorded successfully.", - data=new_dislike.to_dict(), + data={ + 'object': new_dislike.to_dict(), + 'objects_count': blog_service.num_of_dislikes(blog_id) + }, ) diff --git a/api/v1/schemas/blog.py b/api/v1/schemas/blog.py index 1e9da1fdd..70d4f730c 100644 --- a/api/v1/schemas/blog.py +++ b/api/v1/schemas/blog.py @@ -64,15 +64,22 @@ class BlogLikeDislikeCreate(BaseModel): created_at: datetime +class BlogLikeDislikeCreateData(BaseModel): + object: BlogLikeDislikeCreate + objects_count: int # number of likes/dislikes + + class BlogLikeDislikeResponse(BaseModel): status_code: str message: str - data: BlogLikeDislikeCreate + data: BlogLikeDislikeCreateData + class CommentRequest(BaseModel): content: str + class CommentUpdateResponseModel(BaseModel): status: str message: str - data: CommentData \ No newline at end of file + data: CommentData diff --git a/api/v1/services/blog.py b/api/v1/services/blog.py index b65473631..caa4365f3 100644 --- a/api/v1/services/blog.py +++ b/api/v1/services/blog.py @@ -1,6 +1,6 @@ from typing import Optional -from fastapi import HTTPException +from fastapi import HTTPException, status from sqlalchemy.orm import Session from api.core.base.services import Service @@ -117,6 +117,22 @@ def fetch_blog_dislike(self, blog_id: str, user_id: str): .first() ) return blog_dislike + + def check_user_already_liked_blog(self, blog: Blog, user: Blog): + existing_like = self.fetch_blog_like(blog.id, user.id) + if isinstance(existing_like, BlogLike): + raise HTTPException( + detail="You have already liked this blog post", + status_code=status.HTTP_403_FORBIDDEN, + ) + + def check_user_already_disliked_blog(self, blog: Blog, user: Blog): + existing_dislike = self.fetch_blog_dislike(blog.id, user.id) + if isinstance(existing_dislike, BlogDislike): + raise HTTPException( + detail="You have already disliked this blog post", + status_code=status.HTTP_403_FORBIDDEN, + ) def num_of_likes(self, blog_id: str) -> int: """Get the number of likes a blog post has""" diff --git a/tests/v1/blog/test_dislike_blog_post.py b/tests/v1/blog/test_dislike_blog_post.py index 856aea7e2..82a5e2dd4 100644 --- a/tests/v1/blog/test_dislike_blog_post.py +++ b/tests/v1/blog/test_dislike_blog_post.py @@ -3,6 +3,7 @@ from uuid_extensions import uuid7 from sqlalchemy.orm import Session from api.db.database import get_db +from datetime import datetime, timezone from fastapi.testclient import TestClient from unittest.mock import patch, MagicMock from api.v1.services.user import user_service @@ -26,7 +27,7 @@ def mock_user_service(): @pytest.fixture def mock_blog_service(): - with patch("api.v1.services.user.BlogService", autospec=True) as blog_service_mock: + with patch("api.v1.services.blog.BlogService", autospec=True) as blog_service_mock: yield blog_service_mock @@ -54,34 +55,58 @@ def test_blog(test_user): @pytest.fixture() def test_blog_dislike(test_user, test_blog): return BlogDislike( + id=str(uuid7()), user_id=test_user.id, blog_id=test_blog.id, + ip_address="192.168.1.0", + created_at=datetime.now(tz=timezone.utc) ) @pytest.fixture -def access_token_user1(test_user): +def access_token_user(test_user): return user_service.create_access_token(user_id=test_user.id) def make_request(blog_id, token): - return client.put( + return client.post( f"/api/v1/blogs/{blog_id}/dislike", headers={"Authorization": f"Bearer {token}"} ) -# Test for successful dislike + +@patch("api.v1.services.blog.BlogService.create_blog_dislike") def test_successful_dislike( + mock_create_blog_dislike, mock_db_session, test_user, test_blog, - access_token_user1, + test_blog_dislike, + access_token_user ): - mock_user_service.get_current_user = test_user - mock_db_session.query.return_value.filter.return_value.first.return_value = test_blog - mock_db_session.query.return_value.filter_by.return_value.first.return_value = None + # mock current-user AND blog-post + mock_db_session.query().filter().first.side_effect = [test_user, test_blog] - resp = make_request(test_blog.id, access_token_user1) + # mock existing-blog-dislike AND new-blog-dislike + mock_db_session.query().filter_by().first.side_effect = None + + # mock created-blog-dislike + mock_create_blog_dislike.return_value = test_blog_dislike + + # mock dislike-count + mock_db_session.query().filter_by().count.return_value = 1 + + resp = make_request(test_blog.id, access_token_user) + resp_d = resp.json() assert resp.status_code == 200 - assert resp.json()['message'] == "Dislike recorded successfully." + assert resp_d['success'] == True + assert resp_d['message'] == "Dislike recorded successfully." + + dislike_data = resp_d['data']['object'] + assert dislike_data['id'] == test_blog_dislike.id + assert dislike_data['blog_id'] == test_blog.id + assert dislike_data['user_id'] == test_user.id + assert dislike_data['ip_address'] == test_blog_dislike.ip_address + assert datetime.fromisoformat(dislike_data['created_at']) == test_blog_dislike.created_at + assert resp_d['data']['objects_count'] == 1 # Test for double dislike @@ -90,14 +115,14 @@ def test_double_dislike( test_user, test_blog, test_blog_dislike, - access_token_user1, + access_token_user, ): mock_user_service.get_current_user = test_user mock_db_session.query.return_value.filter.return_value.first.return_value = test_blog mock_db_session.query.return_value.filter_by.return_value.first.return_value = test_blog_dislike ### TEST ATTEMPT FOR MULTIPLE DISLIKING... ### - resp = make_request(test_blog.id, access_token_user1) + resp = make_request(test_blog.id, access_token_user) assert resp.status_code == 403 assert resp.json()['message'] == "You have already disliked this blog post" @@ -105,14 +130,14 @@ def test_double_dislike( def test_wrong_blog_id( mock_db_session, test_user, - access_token_user1, + access_token_user, ): mock_user_service.get_current_user = test_user - mock_blog_service.fetch = None + mock_db_session.query().filter().first.return_value = None ### TEST REQUEST WITH WRONG blog_id ### ### using random uuid instead of blog1.id ### - resp = make_request(str(uuid7()), access_token_user1) + resp = make_request(str(uuid7()), access_token_user) assert resp.status_code == 404 assert resp.json()['message'] == "Post not found" @@ -127,4 +152,4 @@ def test_wrong_auth_token( ### TEST ATTEMPT WITH INVALID AUTH... ### resp = make_request(test_blog.id, None) assert resp.status_code == 401 - assert resp.json()['message'] == 'Could not validate credentials' + assert resp.json()['message'] == 'Could not validate credentials' \ No newline at end of file diff --git a/tests/v1/blog/test_like_blog_post.py b/tests/v1/blog/test_like_blog_post.py index e899060df..9544592cf 100644 --- a/tests/v1/blog/test_like_blog_post.py +++ b/tests/v1/blog/test_like_blog_post.py @@ -74,7 +74,9 @@ def make_request(blog_id, token): ) # Test for successful like +@patch("api.v1.services.blog.BlogService.create_blog_like") def test_successful_like( + mock_create_blog_like, mock_db_session, test_user, test_blog, @@ -84,8 +86,14 @@ def test_successful_like( # mock current-user AND blog-post mock_db_session.query().filter().first.side_effect = [test_user, test_blog] - # mock existing-blog-like AND new-blog-like - mock_db_session.query().filter_by().first.side_effect = [None, test_blog_like] + # mock existing-blog-like + mock_db_session.query().filter_by().first.return_value = None + + # mock created-blog-like + mock_create_blog_like.return_value = test_blog_like + + # mock like-count + mock_db_session.query().filter_by().count.return_value = 1 resp = make_request(test_blog.id, access_token_user) resp_d = resp.json() @@ -94,12 +102,13 @@ def test_successful_like( assert resp_d['success'] == True assert resp_d['message'] == "Like recorded successfully." - like_data = resp_d['data'] + like_data = resp_d['data']['object'] assert like_data['id'] == test_blog_like.id assert like_data['blog_id'] == test_blog.id assert like_data['user_id'] == test_user.id assert like_data['ip_address'] == test_blog_like.ip_address assert datetime.fromisoformat(like_data['created_at']) == test_blog_like.created_at + assert resp_d['data']['objects_count'] == 1 # Test for double like @@ -121,12 +130,13 @@ def test_double_like( # Test for wrong blog id def test_wrong_blog_id( + # mock_fetch_blog, mock_db_session, test_user, access_token_user, ): mock_user_service.get_current_user = test_user - mock_blog_service.fetch = None + mock_db_session.query().filter().first.return_value = None ### TEST REQUEST WITH WRONG blog_id ### ### using random uuid instead of blog1.id ###