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

feat: add endpoint to update testimonials for authenticated users #1219

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ce95455
feat: endpoint for updating testimonials
edenis00 Mar 1, 2025
245c949
fix: configure typos and structure rearrange
edenis00 Mar 1, 2025
cc3a2c9
fix: reconstructed thw test_update_testimonial
edenis00 Mar 1, 2025
f007334
Merge pull request #1 from edenis00/feat/update_testimonials_endpoint
edenis00 Mar 1, 2025
3901434
Merge branch 'hngprojects:dev' into dev
edenis00 Mar 1, 2025
3f5cae3
fix: added number to password
edenis00 Mar 1, 2025
7b2509e
fix: change .id to author_id
edenis00 Mar 1, 2025
ddea1f0
Merge pull request #2 from edenis00/feat/update_testimonials_endpoint
edenis00 Mar 1, 2025
bbbe20c
fix: added special character to password input
edenis00 Mar 1, 2025
ecbbddf
Merge branch 'dev' into dev
edenis00 Mar 1, 2025
8f6f3b2
fix: add logging and change response status_code
edenis00 Mar 1, 2025
f4fc4f0
Merge branch 'dev' of https://github.com/edenis00/hng_boilerplate_pyt…
edenis00 Mar 1, 2025
63ce50b
Merge branch 'hngprojects:dev' into dev
edenis00 Mar 1, 2025
7fd66e3
fix: fixed endpoint and register payload
edenis00 Mar 1, 2025
e2ac40d
Merge branch 'dev' of https://github.com/edenis00/hng_boilerplate_pyt…
edenis00 Mar 1, 2025
bd3699b
Merge branch 'dev' into dev
edenis00 Mar 1, 2025
0c275c9
fix: typo in confirm_password
edenis00 Mar 1, 2025
44e7a2e
Merge branch 'dev' into dev
edenis00 Mar 1, 2025
1283d01
fix: fixed access_token logic
edenis00 Mar 1, 2025
81e1c0c
Merge branch 'dev' into dev
edenis00 Mar 1, 2025
f16556d
fix: indentation error
edenis00 Mar 1, 2025
31e2c86
fix: added a mock mail for test
edenis00 Mar 1, 2025
f422ee2
fix: configuring the mock_sender_mail
edenis00 Mar 1, 2025
117fe4a
fix: changes to the mock mail
edenis00 Mar 1, 2025
510618a
fix: repaired the update endpoint
edenis00 Mar 1, 2025
a8cfbcb
fix: added a new testimonial for testing, change the response of the …
edenis00 Mar 1, 2025
7fe8f6f
fix: added a syntax for referrencing the testimonial to update
edenis00 Mar 1, 2025
21312fb
fix: typo error
edenis00 Mar 1, 2025
3083cad
Merge branch 'dev' into dev
edenis00 Mar 1, 2025
495c3e7
fix: logging the db
edenis00 Mar 1, 2025
5e6b60f
Merge branch 'dev' of https://github.com/edenis00/hng_boilerplate_pyt…
edenis00 Mar 1, 2025
a4c0416
fix: logging more errors
edenis00 Mar 1, 2025
ba0dbf4
fix: added a function to create testimonial and doctrings
edenis00 Mar 2, 2025
7d7edb0
Merge branch 'dev' into dev
edenis00 Mar 2, 2025
1182cfe
Merge branch 'dev' into dev
edenis00 Mar 2, 2025
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
60 changes: 29 additions & 31 deletions api/v1/routes/testimonial.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sqlalchemy.orm import Session
from api.v1.models.user import User
from fastapi import Depends, APIRouter, status,Query
from api.utils.success_response import success_response
from api.utils.success_response import success_response, fail_response
from api.v1.services.testimonial import testimonial_service
from api.v1.services.user import user_service
from api.v1.schemas.testimonial import CreateTestimonial
Expand Down Expand Up @@ -81,6 +81,7 @@ async def delete_all_testimonials(

testimonial_service.delete_all(db)


@testimonial.post('/', response_model=success_response)
def create_testimonial(
testimonial_data: CreateTestimonial,
Expand All @@ -96,35 +97,32 @@ def create_testimonial(
)
return response

@testimonial.get("/user/{user_id}", status_code=status.HTTP_200_OK)
def get_user_testimonials(
user_id: str,
page_size: Annotated[int, Query(ge=1, description="Number of testimonials per page")] = 10,
page: Annotated[int, Query(ge=1, description="Page number (starts from 1)")] = 0,
db: Session = Depends(get_db),
current_user: User = Depends(user_service.get_current_user),

@testimonial.put('/{testimonial_id}', response_model=success_response)
def update_testimonial(
testimonial_id: str,
testimonial_data: CreateTestimonial,
db: Annotated[Session, Depends(get_db)],
current_user: User = Depends(user_service.get_current_user)
):
"""Endpoint to get all testimonials for a specific user with pagination"""
try:
# Only check if user is accessing their own testimonials
if user_id != str(current_user.id):
return {
"status_code": 403,
"message": "You can only view your own testimonials"
}

# Simply return the paginated response
return paginated_response(
db=db,
model=Testimonial,
limit=page_size,
skip=max(page, 0),
filters={"author_id": user_id}
'''Endpoint to update testimonial'''
testimonial = testimonial_service.fetch(db, testimonial_id)

if not testimonial:
return fail_response(
status_code=404,
message="Testimonial does not exist"
)

if testimonial.author_id != current_user.id:
return fail_response(
status_code=403,
message="Could not validate credentials"
)
except Exception as e:
logger.exception(f"Error retrieving testimonials: {str(e)}")
return {
"status_code": 500,
"message": "An unexpected error occurred"
}

update_testimonial = testimonial_service.update(db, testimonial_id, testimonial_data)
return success_response(
status_code=200,
message="Your testimonial has been updated successfully.",
data={"id": update_testimonial.id}
)
5 changes: 5 additions & 0 deletions tests/v1/testimonial/test_create_testimonial.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
"content": "Testimonial 2",
"status_code": 201,
},
{
"content": "I love python",
"ratings": 5.02,
"status_code": 201,
},
]

@pytest.fixture(scope='module')
Expand Down
155 changes: 155 additions & 0 deletions tests/v1/testimonial/test_update_testimonial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import uuid
import pytest
from main import app
from fastapi.testclient import TestClient
from unittest.mock import MagicMock, patch
from api.v1.models import Testimonial # noqa: F403

client = TestClient(app)

data = [
{
"client_name": "firsttestclientname",
"author_id": "066a16d8-cab5-7dd3-8000-3a167556bb49",
"content": "I love python",
"id": "066a6e8b-f008-7242-8000-8f090997097c",
"updated_at": "2025-03-01T01:56:31.002967+01:00",
"client_designation": "testclient",
"comments": "I love testimonies",
"ratings": 5.02,
"created_at": "2025-01-01T01:56:31.002967+01:00",
}
]


@pytest.fixture(scope='module')
def mock_send_mail():
""" For mocking the email send functionality """
with patch("api.core.dependencies.email_sender.send_email") as mock_email_sending:
with patch("fastapi.BackgroundTasks.add_task") as add_task_mock:
add_task_mock.side_effect = lambda func, *args, **kwargs: func(*args, **kwargs)
yield mock_email_sending


@pytest.fixture(scope="function")
def client_with_mocks(mock_send_mail):
"""For mocking a database connection and returning a test client"""
with patch('api.db.database.get_db') as mock_get_db:
mock_db = MagicMock()
mock_get_db.return_value = mock_db

mock_db.query.return_value.filter.return_value.first.return_value = None
mock_db.add.reset_mock()
mock_db.commit.reset_mock()
mock_db.refresh.reset_mock()

yield client, mock_db

@pytest.fixture(autouse=True)
def setup_access_token(client_with_mocks):
"""For setting up an access token for authentication"""
client, mock_db = client_with_mocks
mock_db.query.return_value.filter.return_value.first.return_value = None

email = f"test{uuid.uuid4()}@gmail.com"
user_response = client.post(
"/api/v1/auth/register",
json={
"password": "@Testpassword2",
"confirm_password": "@Testpassword2",
"first_name": "Test",
"last_name": "User",
"email": email,
},
)
assert user_response.status_code == 201, f"Setup failed: {user_response.json()}"
return user_response.json()["data"]["access_token"]


def test_create_testimonial(client_with_mocks, setup_access_token):
"""Test to create testimonial"""
client, mock_db = client_with_mocks

payload = {
"content": "I love python",
"ratings": 5.02,
}

response = client.post(
"api/v1/testimonials/",
json=payload,
headers={"Authorization": setup_access_token},
)

assert response.status_code == 201

testimonial_id = response.json()["data"]["id"]
testimonial = MagicMock()
testimonial.content = payload["content"]
testimonial.ratings = payload["ratings"]

mock_db.query(Testimonial).get.return_value = testimonial
retrieved_testimonial = mock_db.query(Testimonial).get(testimonial_id)

assert retrieved_testimonial.content == payload["content"]
assert retrieved_testimonial.ratings == payload["ratings"]

return testimonial_id


def test_update_testimonial_success(client_with_mocks, setup_access_token):
"""Test to update testimonial successfully"""
client, mock_db = client_with_mocks

testimonial_id = test_create_testimonial(client_with_mocks, setup_access_token)

mock_testimonial = MagicMock()
mock_testimonial.id = uuid.UUID(testimonial_id)
mock_testimonial.content = data[0]["content"]
mock_testimonial.client_name = data[0]["client_name"]
mock_testimonial.client_designation = data[0]["client_designation"]
mock_testimonial.comments = data[0]["comments"]
mock_testimonial.ratings = data[0]["ratings"]

mock_db.query.return_value.filter.return_value.first.return_value = mock_testimonial

update_data = {"content": "I love python (updated)"}

response = client.put(
f"/api/v1/testimonials/{data[0]['id']}",
json=update_data,
headers={"Authorization": setup_access_token},
)

assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.json()}"
assert response.json()["message"] == "Your testimonial has been updated successfully."


def test_update_testimonial_not_found(client_with_mocks, setup_access_token):
"""Test to update testimonial that does not exist"""
client, mock_db = client_with_mocks

mock_db.query.return_value.filter.return_value.first.return_value = None

response = client.put(
"/api/v1/testimonials/non_existent_id",
json={"content": "This is an updated testimonial."},
headers={"Authorization": f"Bearer {setup_access_token}"},
)

assert response.status_code == 404
assert response.json()["message"] == "Testimonial does not exist"


def test_update_testimonial_unauthorized(client_with_mocks):
"""Test to update testimonial without authorization"""
client, _ = client_with_mocks

response = client.put(
f"/api/v1/testimonials/{data[0]['id']}",
json={"content": "This is an updated testimonial."},
headers={"Authorization": "Bearer invalid_token"},
)

assert response.status_code == 401
assert response.json()["message"] == "Could not validate credentials"
Loading