diff --git a/api/v1/models/activity_logs.py b/api/v1/models/activity_logs.py index 1c9169158..152cf89ab 100644 --- a/api/v1/models/activity_logs.py +++ b/api/v1/models/activity_logs.py @@ -11,4 +11,4 @@ class ActivityLog(BaseTableModel): action = Column(String, nullable=False) timestamp = Column(DateTime(timezone=True), server_default=func.now()) - user = relationship("User", back_populates="activity_logs") + user = relationship("User", back_populates="activity_logs") \ No newline at end of file diff --git a/api/v1/models/testimonial.py b/api/v1/models/testimonial.py index 332e6ca67..5798193a2 100644 --- a/api/v1/models/testimonial.py +++ b/api/v1/models/testimonial.py @@ -16,3 +16,5 @@ class Testimonial(BaseTableModel): ratings = Column(Float, nullable=True) author = relationship("User", back_populates="testimonials") + + diff --git a/api/v1/routes/testimonial.py b/api/v1/routes/testimonial.py index 87820b736..8b70a5e32 100644 --- a/api/v1/routes/testimonial.py +++ b/api/v1/routes/testimonial.py @@ -2,7 +2,9 @@ """ Module contains CRUD routes for testimonial """ + from fastapi.encoders import jsonable_encoder +from fastapi import HTTPException from api.db.database import get_db from sqlalchemy.orm import Session from api.v1.models.user import User @@ -11,6 +13,7 @@ from api.v1.services.testimonial import testimonial_service from api.v1.services.user import user_service from api.v1.schemas.testimonial import CreateTestimonial +from api.v1.schemas.testimonial import UpdateTestimonial from api.core.responses import SUCCESS from typing import Annotated from api.utils.pagination import paginated_response @@ -96,6 +99,35 @@ def create_testimonial( ) return response + +@testimonial.put("/{testimonial_id}", response_model=success_response) +def update_testimonial( + testimonial_id: str, + testimonial_data: UpdateTestimonial, + db: Annotated[Session, Depends(get_db)], + current_user: User = Depends(user_service.get_current_user) +): + """Endpoint to update a testimonial""" + + existing_testimonial = testimonial_service.fetch(db, testimonial_id) + if not existing_testimonial: + raise HTTPException(status_code=404, detail="Testimonial not found") + + if existing_testimonial.author_id != current_user.id: + raise HTTPException(status_code=403, detail="Not authorized to update this testimonial") + + updated_testimonial = testimonial_service.update(db, testimonial_id, testimonial_data) + + if not updated_testimonial: + raise HTTPException(status_code=500, detail="Failed to update testimonial") + + response = success_response( + status_code=200, + message="Testimonial updated successfully", + data={"id": updated_testimonial.id} + ) + return response + @testimonial.get("/user/{user_id}", status_code=status.HTTP_200_OK) def get_user_testimonials( user_id: str, diff --git a/api/v1/schemas/testimonial.py b/api/v1/schemas/testimonial.py index 91157cb41..e6d254e6c 100644 --- a/api/v1/schemas/testimonial.py +++ b/api/v1/schemas/testimonial.py @@ -1,5 +1,13 @@ from pydantic import BaseModel +from typing import Optional class CreateTestimonial(BaseModel): content: str - ratings: float = 0 \ No newline at end of file + ratings: float = 0 + + +class UpdateTestimonial(BaseModel): + content: Optional[str] = None + ratings: Optional[float] = None + client_name: Optional[str] = None + client_designation: Optional[str] = None diff --git a/api/v1/services/testimonial.py b/api/v1/services/testimonial.py index f3d20352a..77d2cdf33 100644 --- a/api/v1/services/testimonial.py +++ b/api/v1/services/testimonial.py @@ -4,6 +4,8 @@ from api.v1.models.testimonial import Testimonial from api.v1.models.user import User from api.v1.schemas.testimonial import CreateTestimonial +from api.v1.schemas.testimonial import UpdateTestimonial + class TestimonialService(Service): @@ -34,9 +36,19 @@ def fetch(self, db: Session, id: str): return check_model_existence(db, Testimonial, id) - def update(self, db: Session, id: str, schema): + def update(self, db: Session, id: str, schema: UpdateTestimonial): """Updates a testimonial""" - pass + testimonial = self.fetch(db, id) + if not testimonial: + return None + + for key, value in schema.dict(exclude_unset=True).items(): + setattr(testimonial, key, value) + + db.commit() + db.refresh(testimonial) + return testimonial + def delete(self, db: Session, id: str): """Deletes a specific testimonial""" diff --git a/tests/v1/testimonial/test_testimonial_updates.py b/tests/v1/testimonial/test_testimonial_updates.py new file mode 100644 index 000000000..5492237c3 --- /dev/null +++ b/tests/v1/testimonial/test_testimonial_updates.py @@ -0,0 +1,103 @@ +import pytest +from fastapi.testclient import TestClient +from unittest.mock import MagicMock, patch +from uuid_extensions import uuid7 +from datetime import datetime, timezone +from faker import Faker +from main import app +from api.db.database import get_db +from api.v1.models.user import User +from api.v1.models.testimonial import Testimonial +from api.v1.services.user import user_service +from fastapi import status + +fake = Faker() +client = TestClient(app) + +# Fixtures +@pytest.fixture +def db_session_mock(): + db_session = MagicMock() + return db_session + +@pytest.fixture +def client(db_session_mock): + app.dependency_overrides[get_db] = lambda: db_session_mock + client = TestClient(app) + yield client + app.dependency_overrides = {} + + +def mock_get_current_user(): + return User( + id=str(uuid7()), + email=fake.email(), + password=user_service.hash_password("Testpassword@123"), + first_name="Test", + last_name="User", + is_active=True, + is_superadmin=False, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + +def mock_testimonial(user_id): + return Testimonial( + id=str(uuid7()), + content="Original content", + author_id=user_id, + client_name="Client 1", + client_designation="Client Designation", + comments="Testimonial comments", + ratings=4.5 + ) + + +def test_update_testimonial_success(client, db_session_mock): + '''Test successful update of a testimonial''' + + mock_user = mock_get_current_user() + app.dependency_overrides[user_service.get_current_user] = lambda: mock_user + + mock_testimonial_obj = mock_testimonial(mock_user.id) + db_session_mock.get.return_value = mock_testimonial_obj + + update_data = {"content": "Updated content"} + response = client.put( + f'/api/v1/testimonials/{mock_testimonial_obj.id}', + json=update_data, + headers={'Authorization': 'Bearer token'} + ) + + assert response.status_code == 200 + assert response.json()["message"] == "Testimonial updated successfully" + + +def test_update_testimonial_not_found(client, db_session_mock): + '''Test updating a non-existing testimonial''' + + app.dependency_overrides[user_service.get_current_user] = lambda: mock_get_current_user() + + db_session_mock.get.return_value = None + + update_data = {"content": "Updated content"} + testimonial_id = str(uuid7()) + response = client.put( + f'/api/v1/testimonials/{testimonial_id}', + json=update_data, + headers={'Authorization': 'Bearer token'} + ) + + assert response.status_code == 404 + assert response.json()["message"] in ["Testimonial not found", "Testimonial does not exist"] + +def test_update_testimonial_unauthorized(client): + '''Test updating a testimonial without authentication''' + + testimonial_id = str(uuid7()) + update_data = {"content": "Updated content"} + + response = client.put(f'/api/v1/testimonials/{testimonial_id}', json=update_data) + + assert response.status_code == 401 + assert response.json()["message"] == "Not authenticated"