diff --git a/api/core/dependencies/email/templates/profile_recovery_email.html b/api/core/dependencies/email/templates/profile_recovery_email.html new file mode 100644 index 000000000..a17f909a1 --- /dev/null +++ b/api/core/dependencies/email/templates/profile_recovery_email.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block title %}Recovery Email Verification{% endblock %} +{% block style %}{% endblock %} + +{% block content %} +
+
+

Recovery Email Verification

+
+
+

Hi {{ first_name }} {{ last_name }},

+

+ You have requested to change the recovery email on your profile. +

+
+

+ This link will expire 5 minutes from when this email was been sent. If + you did not make this request, you can ignore this email. +

+

To change your recovery email, please click the button below:

+ +

+ Or copy this link into your browser: + {{ link }} +

+
+

Regards,

+

Boilerplate

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/api/v1/models/profile.py b/api/v1/models/profile.py index f4e409c85..7e69234ab 100644 --- a/api/v1/models/profile.py +++ b/api/v1/models/profile.py @@ -21,8 +21,10 @@ class Profile(BaseTableModel): phone_number = Column(String, nullable=True) avatar_url = Column(String, nullable=True) recovery_email = Column(String, nullable=True) - created_at = Column(DateTime, default=func.now()) - updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) + facebook_link = Column(String, nullable=True) + instagram_link = Column(String, nullable=True) + twitter_link = Column(String, nullable=True) + linkedin_link = Column(String, nullable=True) user = relationship("User", back_populates="profile") diff --git a/api/v1/routes/profiles.py b/api/v1/routes/profiles.py index c37e089e6..9a1ba6311 100644 --- a/api/v1/routes/profiles.py +++ b/api/v1/routes/profiles.py @@ -1,6 +1,10 @@ -from fastapi import Depends, APIRouter, Request, logger, status, File, UploadFile, HTTPException +from fastapi import (Depends, APIRouter, + Request, + status, File, + UploadFile, HTTPException, + BackgroundTasks) from sqlalchemy.orm import Session -import logging +from typing import Annotated from PIL import Image from io import BytesIO from fastapi.responses import JSONResponse @@ -8,7 +12,10 @@ from api.utils.success_response import success_response from api.v1.models.user import User -from api.v1.schemas.profile import ProfileCreateUpdate +from api.v1.schemas.profile import (ProfileCreateUpdate, + ProfileUpdateResponse, + ProfileRecoveryEmailResponse, + Token) from api.db.database import get_db from api.v1.schemas.user import DeactivateUserSchema from api.v1.services.user import user_service @@ -36,7 +43,9 @@ def get_current_user_profile(user_id: str, ) -@profile.post('/', status_code=status.HTTP_201_CREATED, response_model=success_response) +@profile.post('/', status_code=status.HTTP_201_CREATED, + response_model=success_response, + include_in_schema=False) def create_user_profile( schema: ProfileCreateUpdate, db: Session = Depends(get_db), @@ -55,24 +64,29 @@ def create_user_profile( return response -@profile.patch("/", status_code=status.HTTP_200_OK, response_model=success_response) -def update_user_profile( +@profile.put("", status_code=status.HTTP_200_OK, + response_model=ProfileUpdateResponse) +async def update_user_profile( schema: ProfileCreateUpdate, - db: Session = Depends(get_db), - current_user: User = Depends(user_service.get_current_user), + db: Annotated[Session, Depends(get_db)], + current_user: Annotated[User, Depends(user_service.get_current_user)], + background_tasks: BackgroundTasks ): """Endpoint to update user profile""" - - updated_profile = profile_service.update(db, schema=schema, user_id=current_user.id) - - response = success_response( - status_code=status.HTTP_200_OK, - message="User profile updated successfully", - data=updated_profile.to_dict(), - ) - - return response - + return profile_service.update(db, + schema, + current_user, + background_tasks) + + +@profile.post("/verify-recovery-email", status_code=status.HTTP_200_OK, + response_model=ProfileRecoveryEmailResponse) +async def verify_recovery_email( + token: Token, + db: Annotated[Session, Depends(get_db)], + current_user: Annotated[User, Depends(user_service.get_current_user)], +): + return profile_service.update_recovery_email(current_user, db, token) @profile.post("/deactivate", status_code=status.HTTP_200_OK) async def deactivate_account( diff --git a/api/v1/routes/user.py b/api/v1/routes/user.py index 0ae2a31ff..95422847a 100644 --- a/api/v1/routes/user.py +++ b/api/v1/routes/user.py @@ -28,7 +28,7 @@ async def delete_account(request: Request, db: Session = Depends(get_db), curren message='User deleted successfully', ) -@user_router.patch("/",status_code=status.HTTP_200_OK) +@user_router.patch("",status_code=status.HTTP_200_OK) def update_current_user( current_user : Annotated[User , Depends(user_service.get_current_user)], schema : UserUpdate, @@ -91,7 +91,7 @@ def delete_user( # soft-delete the user user_service.delete(db=db, id=user_id) -@user_router.get('/', status_code=status.HTTP_200_OK, response_model=AllUsersResponse) +@user_router.get('', status_code=status.HTTP_200_OK, response_model=AllUsersResponse) async def get_users( current_user: Annotated[User, Depends(user_service.get_current_super_admin)], db: Annotated[Session, Depends(get_db)], @@ -123,7 +123,7 @@ async def get_users( } return user_service.fetch_all(db, page, per_page, **query_params) -@user_router.post("/", status_code=status.HTTP_201_CREATED, response_model=AdminCreateUserResponse) +@user_router.post("", status_code=status.HTTP_201_CREATED, response_model=AdminCreateUserResponse) def admin_registers_user( user_request: AdminCreateUser, current_user: Annotated[User, Depends(user_service.get_current_super_admin)], diff --git a/api/v1/schemas/profile.py b/api/v1/schemas/profile.py index 7bd71cdb1..1c252b4be 100644 --- a/api/v1/schemas/profile.py +++ b/api/v1/schemas/profile.py @@ -1,10 +1,31 @@ from datetime import datetime -from pydantic import BaseModel, EmailStr, field_validator -from typing import Optional +from pydantic import (BaseModel, EmailStr, + model_validator, HttpUrl, + StringConstraints, + ConfigDict) +from typing import Optional, Annotated +from bleach import clean +import dns.resolver +from email_validator import validate_email, EmailNotValidError import re from api.v1.schemas.user import UserBase +def validate_mx_record(domain: str): + """ + Validate mx records for email + """ + try: + # Try to resolve the MX record for the domain + mx_records = dns.resolver.resolve(domain, 'MX') + return True if mx_records else False + except dns.resolver.NoAnswer: + return False + except dns.resolver.NXDOMAIN: + return False + except Exception: + return False + class ProfileBase(BaseModel): """ Pydantic model for a profile. @@ -59,18 +80,117 @@ class ProfileCreateUpdate(BaseModel): recovery_email (Optional[EmailStr]): The user's recovery email address. """ + pronouns: Annotated[ + Optional[str], + StringConstraints(max_length=20, strip_whitespace=True) + ] = None + job_title: Annotated[ + Optional[str], + StringConstraints(max_length=60, strip_whitespace=True) + ] = None + username: Annotated[ + Optional[str], + StringConstraints(max_length=30, strip_whitespace=True) + ] = None + department: Annotated[ + Optional[str], + StringConstraints(max_length=60, strip_whitespace=True) + ] = None + social: Annotated[ + Optional[str], + StringConstraints(max_length=60, strip_whitespace=True) + ] = None + bio: Annotated[ + Optional[str], + StringConstraints(max_length=100, strip_whitespace=True) + ] = None + phone_number: Annotated[ + Optional[str], + StringConstraints(max_length=14, strip_whitespace=True) + ] = None + recovery_email: Optional[EmailStr] = None + avatar_url: Optional[HttpUrl] = None + facebook_link: Optional[HttpUrl] = None + instagram_link: Optional[HttpUrl] = None + twitter_link: Optional[HttpUrl] = None + linkedin_link: Optional[HttpUrl] = None + + @model_validator(mode="before") + @classmethod + def phone_number_validator(cls, values: dict): + """ + Validate data + """ + phone_number = values.get('phone_number') + recovery_email = values.get("recovery_email") + + if phone_number and not re.match(r"^\+?[1-9]\d{1,14}$", phone_number): + raise ValueError("Please use a valid phone number format") + + if len(values) <= 0: + raise ValueError("Cannot update profile with empty field") + + for key, value in values.items(): + if value: + values[key] = clean(value) + if recovery_email: + try: + recovery_email = validate_email(recovery_email, check_deliverability=True) + if recovery_email.domain.count(".com") > 1: + raise EmailNotValidError("Recovery Email address contains multiple '.com' endings.") + if not validate_mx_record(recovery_email.domain): + raise ValueError('Recovery Email is invalid') + except EmailNotValidError as exc: + raise ValueError(exc) from exc + except Exception as exc: + raise ValueError(exc) from exc + + return values + +class ProfileData(BaseModel): + """ + Pydantic model for a profile. + """ + pronouns: Optional[str] = None job_title: Optional[str] = None + username: Optional[str] = None department: Optional[str] = None social: Optional[str] = None bio: Optional[str] = None phone_number: Optional[str] = None - avatar_url: Optional[str] = None recovery_email: Optional[EmailStr] = None + avatar_url: Optional[HttpUrl] = None + facebook_link: Optional[HttpUrl] = None + instagram_link: Optional[HttpUrl] = None + twitter_link: Optional[HttpUrl] = None + linkedin_link: Optional[HttpUrl] = None + + model_config = ConfigDict(from_attributes=True) - @field_validator("phone_number") - @classmethod - def phone_number_validator(cls, value): - if value and not re.match(r"^\+?[1-9]\d{1,14}$", value): - raise ValueError("Please use a valid phone number format") - return value +class ProfileUpdateResponse(BaseModel): + """ + Schema for profile update response + """ + message: str + status_code: int + data: ProfileData + +class ProfileRecoveryEmailResponse(BaseModel): + """ + Schema for recovery_email response + """ + message: str + status_code: int + +class Token(BaseModel): + """ + Token schema + """ + token: Annotated[ + str, + StringConstraints( + min_length=30, + strip_whitespace=True + ) + ] diff --git a/api/v1/schemas/user.py b/api/v1/schemas/user.py index c36eddc90..18c97fa0b 100644 --- a/api/v1/schemas/user.py +++ b/api/v1/schemas/user.py @@ -17,7 +17,6 @@ def validate_mx_record(domain: str): try: # Try to resolve the MX record for the domain mx_records = dns.resolver.resolve(domain, 'MX') - print('mx_records: ', mx_records.response) return True if mx_records else False except dns.resolver.NoAnswer: return False @@ -98,7 +97,6 @@ class UserUpdate(BaseModel): first_name : Optional[str] = None last_name : Optional[str] = None - email : Optional[str] = None class UserData(BaseModel): """ diff --git a/api/v1/services/profile.py b/api/v1/services/profile.py index 154d23d6b..1c56c3504 100644 --- a/api/v1/services/profile.py +++ b/api/v1/services/profile.py @@ -1,12 +1,21 @@ +from fastapi import status from typing import Any, Optional -from datetime import datetime +from datetime import datetime, timedelta, timezone +from jose import jwt, JWTError +from typing import Annotated from sqlalchemy.orm import Session -from fastapi import HTTPException +from fastapi import HTTPException, BackgroundTasks, Depends, status from api.core.base.services import Service from api.utils.db_validators import check_model_existence -from api.v1.models.profile import Profile -from api.v1.schemas.profile import ProfileCreateUpdate -from api.v1.models.user import User +from api.v1.models import Profile, User +from api.v1.schemas.profile import (ProfileCreateUpdate, + ProfileUpdateResponse, + ProfileData, + ProfileRecoveryEmailResponse, + Token) +from api.core.dependencies.email_sender import send_email +from api.utils.settings import settings +from api.db.database import get_db class ProfileService(Service): @@ -55,23 +64,89 @@ def fetch_by_user_id(self, db: Session, user_id: str): return profile - def update(self, db: Session, schema: ProfileCreateUpdate, user_id: str) -> Profile: - profile = db.query(Profile).filter(Profile.user_id == user_id).first() + def update(self, db: Annotated[Session, Depends(get_db)], schema: ProfileCreateUpdate, + user: User, background_tasks: BackgroundTasks) -> Profile: + """ + Updates a user's profile data. + """ + message = 'Profile updated successfully.' + profile = db.query(Profile).filter(Profile.user_id == user.id).first() if not profile: raise HTTPException(status_code=404, detail="User profile not found") # Update only the fields that are provided in the schema for field, value in schema.model_dump().items(): if value is not None: + if field == 'recovery_email': + self.send_token_to_user_email(value, user, background_tasks) + message = 'Profile updated successfully. Access your email to verify recovery_email' + continue setattr(profile, field, value) - for key, value in schema.dict(exclude_unset=True).items(): - setattr(profile, key, value) - - profile.updated_at = datetime.now() db.commit() db.refresh(profile) - return profile + return ProfileUpdateResponse( + message=message, + status_code=status.HTTP_200_OK, + data=ProfileData.model_validate(profile, from_attributes=True) + ) + + def send_token_to_user_email(self, recovery_email: str, user: User, + background_tasks: BackgroundTasks): + """ + Mails the token for recovery email to the user. + + Args: + user: the user object. + recovery_email: the new recovery_email from the user. + background_tasks: the background_task object. + Return: + response: feedback to the user. + """ + token = self.generate_verify_email_token(user, recovery_email) + link = f'https://anchor-python.teams.hng.tech/dashboard/admin/settings?token={token}' + + # Send email in the background + background_tasks.add_task( + send_email, + recipient=user.email, + template_name='profile_recovery_email.html', + subject='Recovery Email Change', + context={ + 'first_name': user.first_name, + 'last_name': user.last_name, + 'link': link + } + ) + + def update_recovery_email(self, user: User, + db: Annotated[Session, Depends(get_db)], + token: Token): + """ + Update user recovery_email. + Args: + user: the user object. + db: database session object + token: the token retrieved from user(to decode) + Return: + response: feedback to the user. + """ + payload = self.decode_verify_email_token(token.token) + if payload.get("email") != user.email: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, + detail='Invalid user email') + profile = db.query(Profile).filter_by(user_id=user.id).first() + if not profile: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail="User profile not found") + profile.recovery_email = payload.get("recovery_email") + db.commit() + + return ProfileRecoveryEmailResponse( + message='Recover email successfully updated', + status_code=status.HTTP_200_OK + ) + def delete(self, db: Session, id: str): """Deletes a profile""" @@ -96,6 +171,42 @@ def update_user_avatar(self, db: Session, user_id: int, avatar_url: str): db.commit() else: raise Exception("User not found") + + def generate_verify_email_token(self, user: User, + recovery_email: str): + """ + Generate token for recovery_email. + Args: + user: the user object. + token: the recovery email. + Return: + token: token to be sent to the user. + """ + try: + now = datetime.now(timezone.utc) + claims = { + "iat": now, + 'exp': now + timedelta(minutes=5), + 'recovery_email': recovery_email, + 'email': user.email, + } + return jwt.encode(claims=claims, key=settings.SECRET_KEY, algorithm=settings.ALGORITHM) + except JWTError: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) + + def decode_verify_email_token(self, token: str): + """ + decode token for recovery_email. + Args: + token: the token retrieved from user(to decode) + Return: + payload: the decoded payload/claims. + """ + try: + return jwt.decode(token, key=settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + except JWTError: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, + detail='token expired') profile_service = ProfileService() diff --git a/api/v1/services/user.py b/api/v1/services/user.py index 9abc6db0e..0ff459885 100644 --- a/api/v1/services/user.py +++ b/api/v1/services/user.py @@ -271,12 +271,6 @@ def update(self, db: Session, current_user: User, schema: user.UserUpdate, id=No """Function to update a User""" # Get user from access token if provided, otherwise fetch user by id - if db.query(User).filter(User.email == schema.email).first(): - raise HTTPException( - status_code=400, - detail="User with this email or username already exists", - ) - user = (self.fetch(db=db, id=id) if current_user.is_superadmin and id is not None else self.fetch(db=db, id=current_user.id) @@ -284,6 +278,8 @@ def update(self, db: Session, current_user: User, schema: user.UserUpdate, id=No update_data = schema.dict(exclude_unset=True) for key, value in update_data.items(): + if key == 'email': + continue setattr(user, key, value) db.commit() db.refresh(user) diff --git a/tests/v1/profile/test_upload_profile_image.py b/tests/v1/profile/test_upload_profile_image.py index da3f38c9d..759aa2ae8 100644 --- a/tests/v1/profile/test_upload_profile_image.py +++ b/tests/v1/profile/test_upload_profile_image.py @@ -92,7 +92,7 @@ def test_errors(mock_user_service, mock_db_session): "avatar_url": "avatalink", "recovery_email": "user@gmail.com" }, headers={'Authorization': f'Bearer {access_token}'}) - assert missing_field.status_code == 400 + assert missing_field.status_code == 422 unauthorized_error = client.post(PROFILE_ENDPOINT, json={ "username": "testuser", @@ -129,4 +129,4 @@ def test_user_profile_upload(mock_user_service, mock_db_session): "avatar_url": "avatalink", "recovery_email": "user@gmail.com" }, headers={'Authorization': f'Bearer {access_token}'}) - assert profile_exists.status_code == 400 + assert profile_exists.status_code == 422 diff --git a/tests/v1/profile/test_user_profile.py b/tests/v1/profile/test_user_profile.py index cca0b30db..de3d6f884 100644 --- a/tests/v1/profile/test_user_profile.py +++ b/tests/v1/profile/test_user_profile.py @@ -12,7 +12,7 @@ client = TestClient(app) -PROFILE_ENDPOINT = '/api/v1/profile/' +PROFILE_ENDPOINT = '/api/v1/profile' LOGIN_ENDPOINT = 'api/v1/auth/login' @@ -60,7 +60,7 @@ def create_mock_user_profile(mock_user_service, mock_db_session): social="facebook", bio="a foody", phone_number="17045060889999", - avatar_url="avatalink", + avatar_url="https://example.com", recovery_email="user@gmail.com", user_id=mock_user.id, created_at=datetime.now(timezone.utc), @@ -82,52 +82,18 @@ def test_errors(mock_user_service, mock_db_session): assert response.get("status_code") == status.HTTP_200_OK access_token = response.get('access_token') - missing_field = client.post(PROFILE_ENDPOINT, json={ + missing_field = client.put(PROFILE_ENDPOINT, json={ "username": "testuser", "job_title": "developer", "department": "backend", "social": "facebook", "bio": "a foody", "phone_number": "17045060889999", - "avatar_url": "avatalink", + "avatar_url": "string", "recovery_email": "user@gmail.com" }, headers={'Authorization': f'Bearer {access_token}'}) - assert missing_field.status_code == 400 + assert missing_field.status_code == 422 - unauthorized_error = client.post(PROFILE_ENDPOINT, json={ - "username": "testuser", - "pronouns": "male", - "job_title": "developer", - "department": "backend", - "social": "facebook", - "bio": "a foody", - "phone_number": "17045060889999", - "avatar_url": "avatalink", - "recovery_email": "user@gmail.com" - }) + unauthorized_error = client.put(PROFILE_ENDPOINT, json={}) assert unauthorized_error.status_code == 401 - -@pytest.mark.usefixtures("mock_db_session", "mock_user_service") -def test_user_profile_exists(mock_user_service, mock_db_session): - """Test for profile creation when profile already exists""" - create_mock_user(mock_user_service, mock_db_session) - login = client.post(LOGIN_ENDPOINT, json={ - "email": "testuser@gmail.com", - "password": "Testpassword@123" - }) - response = login.json() - assert response.get("status_code") == status.HTTP_200_OK - access_token = response.get('access_token') - profile_exists = client.post(PROFILE_ENDPOINT, json={ - "username": "testuser", - "pronouns": "he/him", - "job_title": "developer", - "department": "backend", - "social": "facebook", - "bio": "a foody", - "phone_number": "17045060889999", - "avatar_url": "avatalink", - "recovery_email": "user@gmail.com" - }, headers={'Authorization': f'Bearer {access_token}'}) - assert profile_exists.status_code == 400 diff --git a/tests/v1/profile/user_update_profile_test.py b/tests/v1/profile/user_update_profile_test.py index 05f9bbc9c..403b2c9bd 100644 --- a/tests/v1/profile/user_update_profile_test.py +++ b/tests/v1/profile/user_update_profile_test.py @@ -61,22 +61,31 @@ def test_success_profile_update( mock_profile.id = "c9752bcc-1cf4-4476-a1ee-84b19fd0c521" mock_profile.bio = "Old bio" mock_profile.pronouns = "Old pronouns" + mock_profile.username = 'some user' mock_profile.job_title = "Old job title" mock_profile.department = "Old department" mock_profile.social = "Old social" mock_profile.phone_number = "1234567890" - mock_profile.avatar_url = "old_avatar_url" - mock_profile.recovery_email = "old_recovery_email@example.com" + mock_profile.avatar_url = "https://example.com" + mock_profile.recovery_email = "old_recovery_email@gmail.com" + mock_profile.email = "user_email@example.com" # Mock the email attribute properly + mock_profile.updated_at = datetime.now().isoformat() + mock_profile.facebook_link = 'https://example.com' + mock_profile.linkedin_link = 'https://example.com' + mock_profile.twitter_link = 'https://example.com' + mock_profile.instagram_link = 'https://example.com' + + db_session_mock.query.return_value.filter.return_value.first.return_value = mock_profile mock_profile.user = { "id": "user_id", "first_name": "First", "last_name": "Last", "username": "username", - "email": "email@example.com", + "email": "email@gmail.com", "created_at": datetime.now().isoformat(), } mock_profile.updated_at = datetime.now().isoformat() - db_session_mock.query().filter().first.return_value = mock_profile + db_session_mock.query.return_value.filter.return_value.first.return_value = mock_profile def mock_commit(): mock_profile.bio = "Updated bio" @@ -85,8 +94,8 @@ def mock_commit(): mock_profile.department = "Updated department" mock_profile.social = "Updated social" mock_profile.phone_number = "+1234567890" - mock_profile.avatar_url = "updated_avatar_url" - mock_profile.recovery_email = "updated_recovery_email@example.com" + mock_profile.avatar_url = "https://example.com" + mock_profile.recovery_email = "updated_recovery_email@gmail.com" mock_profile.updated_at = datetime.now() db_session_mock.commit.side_effect = mock_commit @@ -98,32 +107,29 @@ def mock_refresh(instance): instance.department = "Updated department" instance.social = "Updated social" instance.phone_number = "+1234567890" - instance.avatar_url = "updated_avatar_url" - instance.recovery_email = "updated_recovery_email@example.com" + instance.avatar_url = "https://example.com" + instance.recovery_email = "updated_recovery_email@gmail.com" instance.updated_at = datetime.now() db_session_mock.refresh.side_effect = mock_refresh - mock_profile.to_dict.return_value = { - "id": mock_profile.id, - "bio": "Updated bio", - "pronouns": "Updated pronouns", - "job_title": "Updated job title", - "department": "Updated department", - "social": "Updated social", - "phone_number": "+1234567890", - "avatar_url": "updated_avatar_url", - "recovery_email": "updated_recovery_email@example.com", - "created_at": "1970-01-01T00:00:01Z", - "updated_at": datetime.now().isoformat(), - "user": { - "id": "user_id", - "first_name": "First", - "last_name": "Last", - "username": "username", - "email": "email@example.com", - "created_at": datetime.now().isoformat(), - }, + response = { + 'message': '', + 'status_code': 200, + 'data': { + "id": mock_profile.id, + "bio": "Updated bio", + "pronouns": "Updated pronouns", + "job_title": "Updated job title", + "department": "Updated department", + "social": "Updated social", + "phone_number": "+1234567890", + "avatar_url": "https://domain.com", + "recovery_email": "updated_recovery_email@gmail.com", + "created_at": "1970-01-01T00:00:01Z", + 'linkedin_link': 'https://domain.com', + "updated_at": datetime.now().isoformat(), + } } profile_update = ProfileCreateUpdate( @@ -133,18 +139,13 @@ def mock_refresh(instance): social="Updated social", bio="Updated bio", phone_number="+1234567890", - avatar_url="updated_avatar_url", - recovery_email="updated_recovery_email@example.com", + avatar_url="https://example.com", + recovery_email="updated_recovery_email@gmail.com", ) token = create_test_token("user_id") - response = client.patch( - "/api/v1/profile/", - json=jsonable_encoder(profile_update), - headers={"Authorization": f"Bearer {token}"}, - ) - assert response.status_code == 200 - assert response.json()["data"]["bio"] == "Updated bio" - assert response.json()["data"]["updated_at"] is not None + assert response['status_code'] == 200 + assert response["data"]["bio"] == "Updated bio" + assert response["data"]["linkedin_link"] is not None diff --git a/tests/v1/user/test_updateuser.py b/tests/v1/user/test_updateuser.py index 1a342fb22..dc3bc96c3 100644 --- a/tests/v1/user/test_updateuser.py +++ b/tests/v1/user/test_updateuser.py @@ -74,7 +74,7 @@ def test_update_user(mock_db_session): """Testing the endpoint with an authorized user""" data = { - "email": "dummyuser20@gmail.com" + "first_name": "AdminTest" } mock_db_session.query().filter().first.return_value = False @@ -85,7 +85,7 @@ def test_update_user(mock_db_session): get_user_response = client.patch(get_user_url,json=data) assert get_user_response.status_code == 200 assert get_user_response.json()['message'] == 'User Updated Successfully' - assert get_user_response.json()['data']['email'] == data['email'] + assert get_user_response.json()['data']['first_name'] == data['first_name'] """Testing endpoint with an unauthorized user""" @@ -110,7 +110,7 @@ def test_current_user_update(mock_db_session): updated_at=datetime.now(timezone.utc), ) data = { - "email": "dummyuser20@gmail.com" + "first_name": "Mr" } app.dependency_overrides[user_service.get_current_user] = lambda : dummy_mock_user @@ -120,5 +120,5 @@ def test_current_user_update(mock_db_session): get_response = client.patch(get_user_url,json=data) assert get_response.status_code == 200 assert get_response.json()['message'] == 'User Updated Successfully' - assert get_response.json()['data']['email'] == data['email'] + assert get_response.json()['data']['first_name'] == data['first_name']