-
diff --git a/tests/test_user.py b/tests/test_user.py
index d6f42d0..674a1c1 100644
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -13,7 +13,9 @@ def test_update_profile_unauthorized(unauth_client: TestClient):
data={
"name": "New Name",
"email": "new@example.com",
- "avatar_url": "https://example.com/avatar.jpg"
+ },
+ files={
+ "avatar_file": ("test_avatar.jpg", b"fake image data", "image/jpeg")
},
follow_redirects=False
)
@@ -23,14 +25,40 @@ def test_update_profile_unauthorized(unauth_client: TestClient):
def test_update_profile_authorized(auth_client: TestClient, test_user: User, session: Session):
"""Test that authorized users can edit their profile"""
-
+
+ # Create test image data
+ test_image_data = b"fake image data"
+
# Update profile
response: Response = auth_client.post(
app.url_path_for("update_profile"),
data={
"name": "Updated Name",
"email": "updated@example.com",
- "avatar_url": "https://example.com/new-avatar.jpg"
+ },
+ files={
+ "avatar_file": ("test_avatar.jpg", test_image_data, "image/jpeg")
+ },
+ follow_redirects=False
+ )
+ assert response.status_code == 303
+ assert response.headers["location"] == app.url_path_for("read_profile")
+
+ # Verify changes in database
+ session.refresh(test_user)
+ assert test_user.name == "Updated Name"
+ assert test_user.email == "updated@example.com"
+ assert test_user.avatar_data == test_image_data
+ assert test_user.avatar_content_type == "image/jpeg"
+
+
+def test_update_profile_without_avatar(auth_client: TestClient, test_user: User, session: Session):
+ """Test that profile can be updated without changing the avatar"""
+ response: Response = auth_client.post(
+ app.url_path_for("update_profile"),
+ data={
+ "name": "Updated Name",
+ "email": "updated@example.com",
},
follow_redirects=False
)
@@ -41,7 +69,6 @@ def test_update_profile_authorized(auth_client: TestClient, test_user: User, ses
session.refresh(test_user)
assert test_user.name == "Updated Name"
assert test_user.email == "updated@example.com"
- assert test_user.avatar_url == "https://example.com/new-avatar.jpg"
def test_delete_account_unauthorized(unauth_client: TestClient):
@@ -62,7 +89,7 @@ def test_delete_account_wrong_password(auth_client: TestClient, test_user: User)
data={"confirm_delete_password": "WrongPassword123!"},
follow_redirects=False
)
- assert response.status_code == 400
+ assert response.status_code == 422
assert "Password is incorrect" in response.text.strip()
@@ -81,3 +108,37 @@ def test_delete_account_success(auth_client: TestClient, test_user: User, sessio
# Verify user is deleted from database
user = session.get(User, test_user.id)
assert user is None
+
+
+def test_get_avatar_authorized(auth_client: TestClient, test_user: User):
+ """Test getting user avatar"""
+ # First upload an avatar
+ test_image_data = b"fake image data"
+ auth_client.post(
+ app.url_path_for("update_profile"),
+ data={
+ "name": test_user.name,
+ "email": test_user.email,
+ },
+ files={
+ "avatar_file": ("test_avatar.jpg", test_image_data, "image/jpeg")
+ },
+ )
+
+ # Then try to retrieve it
+ response = auth_client.get(
+ app.url_path_for("get_avatar")
+ )
+ assert response.status_code == 200
+ assert response.content == test_image_data
+ assert response.headers["content-type"] == "image/jpeg"
+
+
+def test_get_avatar_unauthorized(unauth_client: TestClient):
+ """Test getting avatar for non-existent user"""
+ response = unauth_client.get(
+ app.url_path_for("get_avatar"),
+ follow_redirects=False
+ )
+ assert response.status_code == 303
+ assert response.headers["location"] == app.url_path_for("read_login")
diff --git a/utils/models.py b/utils/models.py
index 63d9ec2..29e468e 100644
--- a/utils/models.py
+++ b/utils/models.py
@@ -5,7 +5,7 @@
from typing import Optional, List, Union
from fastapi import HTTPException
from sqlmodel import SQLModel, Field, Relationship
-from sqlalchemy import Column, Enum as SQLAlchemyEnum
+from sqlalchemy import Column, Enum as SQLAlchemyEnum, LargeBinary
from sqlalchemy.orm import Mapped
logger = getLogger("uvicorn.error")
@@ -180,7 +180,9 @@ class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
email: str = Field(index=True, unique=True)
- avatar_url: Optional[str] = None
+ avatar_data: Optional[bytes] = Field(
+ default=None, sa_column=Column(LargeBinary))
+ avatar_content_type: Optional[str] = None
created_at: datetime = Field(default_factory=utc_time)
updated_at: datetime = Field(default_factory=utc_time)