Skip to content

Commit

Permalink
Merge pull request #936 from traderstechie/fix/corrections-in-blog-li…
Browse files Browse the repository at this point in the history
…ke-and-dislike-implementation

fix: corrections in blog like and dislike implementation
  • Loading branch information
johnson-oragui authored Aug 23, 2024
2 parents 9071502 + c1cd498 commit d5a6316
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 70 deletions.
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

0 comments on commit d5a6316

Please sign in to comment.