10000 Merge pull request #7 from code4mk/feature/pytest-implement · code4mk/fastapi-template@f81309d · GitHub
[go: up one dir, main page]

Skip to content

Commit f81309d

Browse files
authored
Merge pull request #7 from code4mk/feature/pytest-implement
test: add the user flow testcase
2 parents 6f1b4a4 + 1b9460b commit f81309d

File tree

13 files changed

+378
-1
lines changed

13 files changed

+378
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ docker-compose.override.yml
6666
# Documentation
6767
docs/_build/
6868

69-
app/tests/
7069

7170
fastapi-pundra/
7271
fastapi-pundra1/

app/tests/__init__.py

Whitespace-only changes.

app/tests/conftest.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import pytest
2+
import os
3+
from fastapi.testclient import TestClient
4+
from sqlalchemy import create_engine, inspect
5+
from sqlalchemy.orm import sessionmaker
6+
from dotenv import load_dotenv
7+
8+
from app.database.database import Base, get_db_session
9+
from app.main import create_application
10+
11+
load_dotenv()
12+
13+
# Test database URL
14+
TEST_DATABASE_URL = os.getenv("TEST_DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/postgres") B41A
15+
16+
# Create test engine
17+
engine = create_engine(TEST_DATABASE_URL)
18+
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
19+
20+
@pytest.fixture(scope="session", autouse=True)
21+
def setup_test_database():
22+
# Create all tables
23+
Base.metadata.create_all(bind=engine)
24+
25+
# Print all table names and their columns
26+
inspector = inspect(engine)
27+
table_names = inspector.get_table_names()
28+
print("\nDatabase Schema:")
29+
print("=" * 50)
30+
for index, table in enumerate(table_names, 1):
31+
print(f"\n{index}: {table}")
32+
print("\n" + "=" * 50)
33+
34+
yield
35+
# Drop all tables after all tests
36+
Base.metadata.drop_all(bind=engine)
37+
38+
@pytest.fixture(scope="session")
39+
def app():
40+
# Set up
41+
app = create_application()
42+
43+
# Override the get_db_session dependency
44+
def override_get_db_session():
45+
db = TestingSessionLocal()
46+
try:
47+
yield db
48+
finally:
49+
db.close()
50+
51+
app.dependency_overrides[get_db_session] = override_get_db_session
52+
return app
53+
54+
55+
@pytest.fixture(scope="session")
56+
def client(app):
57+
return TestClient(app)
58+
59+
60+
@pytest.fixture(scope="function")
61+
def db():
62+
try:
63+
# Get database session
64+
db = TestingSessionLocal()
65+
yield db
66+
finally:
67+
pass
68+
db.close()

app/tests/factories/__init__.py

Whitespace-only changes.

app/tests/factories/user_factory.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import uuid
2+
3+
import factory
4+
from factory.fuzzy import FuzzyText
5+
6+
from app.models.users import User
7+
8+
9+
class UserFactory(factory.Factory):
10+
class Meta:
11+
model = User
12+
13+
id = factory.LazyFunction(uuid.uuid4)
14+
name = factory.Faker("name")
15+
email = factory.Faker("email")
16+
password = factory.LazyFunction(lambda: "hashed_" + FuzzyText(length=10).fuzz())
17+
status = "active"

app/tests/fixtures/__init__.py

Whitespace-only changes.

app/tests/fixtures/common.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from unittest.mock import MagicMock
2+
from fastapi import UploadFile
3+
from io import BytesIO
4+
import pytest
5+
6+
@pytest.fixture
7+
def mock_file_image():
8+
"""
9+
Creates a mock UploadFile that simulates an image upload.
10+
Returns a MagicMock object that mimics UploadFile behavior with image-specific attributes.
11+
"""
12+
# Create a small 1x1 black pixel in PNG format
13+
image_data = (
14+
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00'
15+
b'\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc'
16+
b'\x00\x00\x00\x02\x00\x01\xe5\x27\xde\xfc\x00\x00\x00\x00IEND\xaeB`\x82'
17+
)
18+
19+
mock_file = MagicMock(spec=UploadFile)
20+
mock_file.filename = "test_image.png"
21+
mock_file.content_type = "image/png"
22+
mock_file.file = BytesIO(image_data)
23+
return mock_file
24+
25+
@pytest.fixture
26+
def mock_file_video():
27+
"""
28+
Creates a mock UploadFile that simulates a video upload.
29+
Returns a MagicMock object that mimics UploadFile behavior with video-specific attributes.
30+
"""
31+
# Create a minimal valid video file bytes
32+
E377 video_data = b'FaKeViDeOdAtA' # This is just a placeholder, not actual video data
33+
34+
mock_file = MagicMock(spec=UploadFile)
35+
mock_file.filename = "test_video.mp4"
36+
mock_file.content_type = "video/mp4"
37+
mock_file.file = BytesIO(video_data)
38+
return mock_file
39+
40+
@pytest.fixture
41+
def mock_file_pdf():
42+
"""
43+
Creates a mock UploadFile that simulates a PDF upload.
44+
Returns a MagicMock object that mimics UploadFile behavior with PDF-specific attributes.
45+
"""
46+
# Create a minimal valid PDF file bytes
47+
pdf_data = b'%PDF-1.4\n%EOF' # Minimal PDF file structure
48+
49+
mock_file = MagicMock(spec=UploadFile)
50+
mock_file.filename = "test_document.pdf"
51+
mock_file.content_type = "application/pdf"
52+
mock_file.file = BytesIO(pdf_data)
53+
return mock_file

app/tests/integration/__init__.py

Whitespace-only changes.

app/tests/integration/test_health.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from fastapi import status
2+
3+
4+
def test_health_check(client):
5+
response = client.get("/health")
6+
assert response.status_code == status.HTTP_200_OK
7+
assert response.json() == {"status": "ok"}
8+
9+
10+
def test_root_index(client):
11+
response = client.get("/")
12+
assert response.status_code == status.HTTP_200_OK
13+
assert response.json() == {"message": "FastAPI is running..."}
14+
15+
16+
def test_the_index(client):
17+
response = client.get("/the-index")
18+ F438
assert response.status_code == status.HTTP_200_OK
19+
assert response.json() == {"message": "FastAPI is running..."}

app/tests/integration/test_user.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from fastapi import status
2+
3+
4+
def test_user_registration(client):
5+
user_data = {"name": "Test User", "email": "test@example.com", "password": "password123"}
6+
7+
response = client.post("/api/v1/users/registration", json=user_data)
8+
print(response.json())
9+
assert response.status_code == status.HTTP_201_CREATED
10+
data = response.json()
11+
assert data["message"] == "Registration successful"
12+
assert data["user"]["email"] == user_data["email"]
13+
14+
15+
def test_user_login(client):
16+
17+
# Then try to login
18+
login_data = {"email": "test@example.com", "password": "password123"}
19+
20+
response = client.post("/api/v1/users/login", json=login_data)
21+
print(response.json())
22+
assert response.status_code == status.HTTP_200_OK
23+
data = response.json()
24+
assert data["message"] == "Login successful"
25+
assert "access_token" in data
26+
assert "refresh_token" in data

app/tests/unit/__init__.py

Whitespace-only changes.

app/tests/unit/test_user_services.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import pytest
2+
from unittest.mock import Mock, patch
3+
from fastapi import Request
4+
from app.services.user_service import UserService
5+
from app.tests.factories.user_factory import UserFactory
6+
from fastapi_pundra.rest.exceptions import ItemNotFoundException, UnauthorizedException, BaseAPIException
7+
from app.schemas.user_schema import UserCreateSchema
8+
from app.models.users import User
9+
from fastapi_pundra.common.password import compare_hashed_password
10+
11+
@pytest.fixture
12+
def user_service():
13+
"""Return a UserService instance."""
14+
return UserService()
15+
16+
@pytest.mark.asyncio
17+
async def test_get_users(user_service, db):
18+
# Create some test users
19+
users = [UserFactory(status="active") for _ in range(3)]
20+
users.extend([UserFactory(status="inactive") for _ in range(2)])
21+
for user in users:
22+
db.add(user)
23+
db.commit()
24+
25+
# Mock request
26+
mock_request = Mock(spec=Request)
27+
mock_request.query_params = {}
28+
29+
result = await user_service.s_get_users(mock_request, db)
30+
assert "users" in result
31+
assert len(result["users"]) == 6
32+
33+
@pytest.mark.asyncio
34+
async def test_registration_success(user_service, db):
35+
mock_request = Mock(spec=Request)
36+
user_data = UserCreateSchema(
37+
email="mostafa@example.com",
38+
password="password123",
39+
name="Test User mostafa"
40+
)
41+
42+
result = await user_service.s_registration(mock_request, db, user_data)
43+
44+
assert result["message"] == "Registration successful"
45+
assert result["user"]["email"] == "mostafa@example.com"
46+
assert result["user"]["name"] == "Test User mostafa"
47+
assert result["user"]["status"] == "active"
48+
49+
@pytest.mark.asyncio
50+
async def test_registration_duplicate_email(user_service, db):
51+
mock_request = Mock(spec=Request)
52+
user_data = UserCreateSchema(
53+
email="mostafa@example.com", # Using the same email as existing_user
54+
password="password123",
55+
name="Test User mostafa"
56+
)
57+
58+
with pytest.raises(BaseAPIException) as exc:
59+
await user_service.s_registration(mock_request, db, user_data)
60+
assert str(exc.value.message) == "Email already registered" # Fixed assertion to be outside the with block
61+
62+
@pytest.mark.asyncio
63+
async def test_get_user_by_id(user_service, db):
64+
user = UserFactory()
65+
db.add(user)
66+
db.commit()
67+
68+
mock_request = Mock(spec=Request)
69+
result = await user_service.s_get_user_by_id(mock_request, db, str(user.id))
70+
assert result["id"] == str(user.id)
71+
72+
@pytest.mark.asyncio
73+
async def test_get_user_by_id_not_found(user_service, db):
74+
mock_request = Mock(spec=Request)
75+
nonexistent_uuid = "123e4567-e89b-12d3-a456-426614174000" # Valid UUID format
76+
with pytest.raises(ItemNotFoundException):
77+
await user_service.s_get_user_by_id(mock_request, db, nonexistent_uuid)
78+
79+
@pytest.mark.asyncio
80+
async def test_login_success(user_service, db):
81+
password = "password123"
82+
email = "mostafa@example.com"
83+
84+
mock_request = Mock(spec=Request)
85+
mock_request.json = Mock(return_value={"email": email, "password": password})
86+
87+
with patch('app.services.user_service.the_query') as mock_the_query:
88+
mock_the_query.return_value = {"email": email, "password": password}
89+
result = await user_service.s_login(mock_request, db)
90+
91+
assert result["message"] == "Login successful"
92+
assert "access_token" in result
93+
assert "refresh_token" in result
94+
assert result["user"]["email"] == email
95+
96+
@pytest.mark.asyncio
97+
async def test_login_invalid_credentials(user_service, db):
98+
user = UserFactory()
99+
db.add(user)
100+
db.commit()
101+
102+
mock_request = Mock(spec=Request)
103+
with patch('app.services.user_service.the_query') as mock_the_query:
104+
mock_the_query.return_value = {"email": user.email, "password": "wrong_password"}
105+
with pytest.raises(UnauthorizedException):
106+
await user_service.s_login(mock_request, db)
107+
108+
@pytest.mark.asyncio
109+
async def test_update_user(user_service, db):
110+
user = UserFactory()
111+
db.add(user)
112+
db.commit()
113+
114+
mock_request = Mock(spec=Request)
115+
new_name = "Updated Name"
116+
117+
with patch('app.services.user_service.the_query') as mock_the_query:
118+
mock_the_query.return_value = {"name": new_name}
119+
result = await user_service.s_update_user(mock_request, db, str(user.id))
120+
121+
assert result["message"] == "User updated successfully"
122+
assert result["user"]["name"] == new_name
123+
124+
@pytest.mark.asyncio
125+
async def test_update_user_not_found(user_service, db):
126+
mock_request = Mock(spec=Request)
127+
nonexistent_uuid = "123e4567-e89b-12d3-a456-426614174000" # Valid UUID format
128+
with patch('app.services.user_service.the_query') as mock_the_query:
129+
mock_the_query.return_value = {"name": "New Name"}
130+
with pytest.raises(ItemNotFoundException):
131+
await user_service.s_update_user(mock_request, db, nonexistent_uuid)
132+
133+
@pytest.mark.asyncio
134+
async def test_delete_user(user_service, db):
135+
user = UserFactory()
136+
db.add(user)
137+
db.commit()
138+
139+
mock_request = Mock(spec=Request)
140+
result = await user_service.s_delete_user(mock_request, db, str(user.id))
141+
assert result["message"] == "User deleted successfully"
142+
143+
# Verify user is deleted
144+
deleted_user = db.query(User).filter_by(id=user.id).first()
145+
assert deleted_user is None
146+
147+
@pytest.mark.asyncio
148+
async def test_delete_user_not_found(user_service, db):
149+
mock_request = Mock(spec=Request)
150+
nonexistent_uuid = "123e4567-e89b-12d3-a456-426614174000" # Valid UUID format
151+
with pytest.raises(ItemNotFoundException):
152+
await user_service.s_delete_user(mock_request, db, nonexistent_uuid)
153+
154+
@pytest.mark.asyncio
155+
async def test_update_user_email_and_password(user_service, db):
156+
user = UserFactory()
157+
db.add(user)
158+
db.commit()
159+
160+
mock_request = Mock(spec=Request)
161+
new_email = "newemail@example.com"
162+
new_password = "newpassword123"
163+
164+
with patch('app.services.user_service.the_query') as mock_the_query:
165+
mock_the_query.return_value = {
166+
"email": new_email,
167+
"password": new_password
168+
}
169+
result = await user_service.s_update_user(mock_request, db, str(user.id))
170+
171+
assert result["message"] == "User updated successfully"
172+
assert result["user"]["email"] == new_email
173+
174+
# Verify password was hashed and updated
175+
updated_user = db.query(User).filter_by(id=user.id).first()
176+
assert compare_hashed_password(new_password, updated_user.password)

app/tests/unit/test_users_models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from app.models.users import User
2+
from app.tests.factories.user_factory import UserFactory
3+
4+
5+
def test_user_model_creation():
6+
user = UserFactory.build()
7+
assert isinstance(user, User)
8+
assert user.email is not None
9+
assert user.name is not None
10+
assert user.status == "active"
11+
12+
13+
def test_user_as_dict():
14+
user = UserFactory.build()
15+
user_dict = user.as_dict()
16+
assert isinstance(user_dict, dict)
17+
assert "id" in user_dict
18+
assert "email" in user_dict
19+
assert "name" in user_dict

0 commit comments

Comments
 (0)
0