Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: corrections in blog like and dislike implementation #936

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 47 additions & 47 deletions api/v1/routes/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
)


Expand Down
11 changes: 9 additions & 2 deletions api/v1/schemas/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
data: CommentData
18 changes: 17 additions & 1 deletion api/v1/services/blog.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"""
Expand Down
57 changes: 41 additions & 16 deletions tests/v1/blog/test_dislike_blog_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -90,29 +115,29 @@ 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"

# Test for wrong blog id
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"

Expand All @@ -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'
18 changes: 14 additions & 4 deletions tests/v1/blog/test_like_blog_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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 ###
Expand Down
Loading