Skip to content

Commit a6fd524

Browse files
New test fixtures for authed and unauthed clients, tests for user endpoints
1 parent 2fe1570 commit a6fd524

File tree

3 files changed

+155
-57
lines changed

3 files changed

+155
-57
lines changed

tests/conftest.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi.testclient import TestClient
55
from utils.db import get_connection_url, set_up_db, tear_down_db, get_session
66
from utils.models import User, PasswordResetToken
7-
from utils.auth import get_password_hash
7+
from utils.auth import get_password_hash, create_access_token, create_refresh_token
88
from main import app
99

1010
load_dotenv()
@@ -54,22 +54,6 @@ def clean_db(session: Session):
5454
session.commit()
5555

5656

57-
# Test client fixture
58-
@pytest.fixture()
59-
def client(session: Session):
60-
"""
61-
Provides a TestClient instance with the session fixture.
62-
Overrides the get_session dependency to use the test session.
63-
"""
64-
def get_session_override():
65-
return session
66-
67-
app.dependency_overrides[get_session] = get_session_override
68-
client = TestClient(app)
69-
yield client
70-
app.dependency_overrides.clear()
71-
72-
7357
# Test user fixture
7458
@pytest.fixture()
7559
def test_user(session: Session):
@@ -85,3 +69,41 @@ def test_user(session: Session):
8569
session.commit()
8670
session.refresh(user)
8771
return user
72+
73+
74+
# Unauthenticated client fixture
75+
@pytest.fixture()
76+
def unauth_client(session: Session):
77+
"""
78+
Provides a TestClient instance without authentication.
79+
"""
80+
def get_session_override():
81+
return session
82+
83+
app.dependency_overrides[get_session] = get_session_override
84+
client = TestClient(app)
85+
yield client
86+
app.dependency_overrides.clear()
87+
88+
89+
# Authenticated client fixture
90+
@pytest.fixture()
91+
def auth_client(session: Session, test_user: User):
92+
"""
93+
Provides a TestClient instance with valid authentication tokens.
94+
"""
95+
def get_session_override():
96+
return session
97+
98+
app.dependency_overrides[get_session] = get_session_override
99+
client = TestClient(app)
100+
101+
# Create and set valid tokens
102+
access_token = create_access_token({"sub": test_user.email})
103+
refresh_token = create_refresh_token({"sub": test_user.email})
104+
105+
client.cookies.set("access_token", access_token)
106+
client.cookies.set("refresh_token", refresh_token)
107+
108+
yield client
109+
app.dependency_overrides.clear()

tests/test_authentication.py

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def test_invalid_token_type():
8686
# --- API Endpoint Tests ---
8787

8888

89-
def test_register_endpoint(client: TestClient, session: Session):
90-
response = client.post(
91-
"/auth/register",
89+
def test_register_endpoint(unauth_client: TestClient, session: Session):
90+
response = unauth_client.post(
91+
app.url_path_for("register"),
9292
data={
9393
"name": "New User",
9494
"email": "[email protected]",
@@ -107,9 +107,9 @@ def test_register_endpoint(client: TestClient, session: Session):
107107
assert verify_password("NewPass123!@#", user.hashed_password)
108108

109109

110-
def test_login_endpoint(client: TestClient, test_user: User):
111-
response = client.post(
112-
"/auth/login",
110+
def test_login_endpoint(unauth_client: TestClient, test_user: User):
111+
response = unauth_client.post(
112+
app.url_path_for("login"),
113113
data={
114114
"email": test_user.email,
115115
"password": "Test123!@#"
@@ -124,18 +124,18 @@ def test_login_endpoint(client: TestClient, test_user: User):
124124
assert "refresh_token" in cookies
125125

126126

127-
def test_refresh_token_endpoint(client: TestClient, test_user: User):
128-
# Create expired access token and valid refresh token
129-
access_token = create_access_token(
127+
def test_refresh_token_endpoint(auth_client: TestClient, test_user: User):
128+
# Override just the access token to be expired, keeping the valid refresh token
129+
expired_access_token = create_access_token(
130130
{"sub": test_user.email},
131131
timedelta(minutes=-10)
132132
)
133-
refresh_token = create_refresh_token({"sub": test_user.email})
133+
auth_client.cookies.set("access_token", expired_access_token)
134134

135-
client.cookies.set("access_token", access_token)
136-
client.cookies.set("refresh_token", refresh_token)
137-
138-
response = client.post("/auth/refresh", follow_redirects=False)
135+
response = auth_client.post(
136+
app.url_path_for("refresh_token"),
137+
follow_redirects=False
138+
)
139139
assert response.status_code == 303
140140

141141
# Check for new tokens in headers
@@ -155,10 +155,10 @@ def test_refresh_token_endpoint(client: TestClient, test_user: User):
155155
assert decoded["sub"] == test_user.email
156156

157157

158-
def test_password_reset_flow(client: TestClient, session: Session, test_user: User, mock_resend_send):
158+
def test_password_reset_flow(unauth_client: TestClient, session: Session, test_user: User, mock_resend_send):
159159
# Test forgot password request
160-
response = client.post(
161-
"/auth/forgot_password",
160+
response = unauth_client.post(
161+
app.url_path_for("forgot_password"),
162162
data={"email": test_user.email},
163163
follow_redirects=False
164164
)
@@ -188,8 +188,8 @@ def test_password_reset_flow(client: TestClient, session: Session, test_user: Us
188188
assert not reset_token.used
189189

190190
# Test password reset
191-
response = client.post(
192-
"/auth/reset_password",
191+
response = unauth_client.post(
192+
app.url_path_for("reset_password"),
193193
data={
194194
"email": test_user.email,
195195
"token": reset_token.token,
@@ -207,12 +207,11 @@ def test_password_reset_flow(client: TestClient, session: Session, test_user: Us
207207
assert reset_token.used
208208

209209

210-
def test_logout_endpoint(client: TestClient):
211-
# First set some cookies
212-
client.cookies.set("access_token", "some_access_token")
213-
client.cookies.set("refresh_token", "some_refresh_token")
214-
215-
response = client.get("/auth/logout", follow_redirects=False)
210+
def test_logout_endpoint(auth_client: TestClient):
211+
response = auth_client.get(
212+
app.url_path_for("logout"),
213+
follow_redirects=False
214+
)
216215
assert response.status_code == 303
217216

218217
# Check for cookie deletion in headers
@@ -226,9 +225,9 @@ def test_logout_endpoint(client: TestClient):
226225
# --- Error Case Tests ---
227226

228227

229-
def test_register_with_existing_email(client: TestClient, test_user: User):
230-
response = client.post(
231-
"/auth/register",
228+
def test_register_with_existing_email(unauth_client: TestClient, test_user: User):
229+
response = unauth_client.post(
230+
app.url_path_for("register"),
232231
data={
233232
"name": "Another User",
234233
"email": test_user.email,
@@ -239,9 +238,9 @@ def test_register_with_existing_email(client: TestClient, test_user: User):
239238
assert response.status_code == 400
240239

241240

242-
def test_login_with_invalid_credentials(client: TestClient, test_user: User):
243-
response = client.post(
244-
"/auth/login",
241+
def test_login_with_invalid_credentials(unauth_client: TestClient, test_user: User):
242+
response = unauth_client.post(
243+
app.url_path_for("login"),
245244
data={
246245
"email": test_user.email,
247246
"password": "WrongPass123!@#"
@@ -250,9 +249,9 @@ def test_login_with_invalid_credentials(client: TestClient, test_user: User):
250249
assert response.status_code == 400
251250

252251

253-
def test_password_reset_with_invalid_token(client: TestClient, test_user: User):
254-
response = client.post(
255-
"/auth/reset_password",
252+
def test_password_reset_with_invalid_token(unauth_client: TestClient, test_user: User):
253+
response = unauth_client.post(
254+
app.url_path_for("reset_password"),
256255
data={
257256
"email": test_user.email,
258257
"token": "invalid_token",
@@ -263,7 +262,7 @@ def test_password_reset_with_invalid_token(client: TestClient, test_user: User):
263262
assert response.status_code == 400
264263

265264

266-
def test_password_reset_url_generation(client: TestClient):
265+
def test_password_reset_url_generation(unauth_client: TestClient):
267266
"""
268267
Tests that the password reset URL is correctly formatted and contains
269268
the required query parameters.
@@ -290,12 +289,12 @@ def test_password_reset_url_generation(client: TestClient):
290289
assert query_params["token"][0] == test_token
291290

292291

293-
def test_password_reset_email_url(client: TestClient, session: Session, test_user: User, mock_resend_send):
292+
def test_password_reset_email_url(unauth_client: TestClient, session: Session, test_user: User, mock_resend_send):
294293
"""
295294
Tests that the password reset email contains a properly formatted reset URL.
296295
"""
297-
response = client.post(
298-
"/auth/forgot_password",
296+
response = unauth_client.post(
297+
app.url_path_for("forgot_password"),
299298
data={"email": test_user.email},
300299
follow_redirects=False
301300
)

tests/test_user.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,83 @@
1-
import pytest
21
from fastapi.testclient import TestClient
3-
from sqlmodel import Session, select
2+
from fastapi.responses import Response
3+
from sqlmodel import Session
44

55
from main import app
66
from utils.models import User
7+
8+
9+
def test_update_profile_unauthorized(unauth_client: TestClient):
10+
"""Test that unauthorized users cannot edit profile"""
11+
response: Response = unauth_client.post(
12+
app.url_path_for("update_profile"),
13+
data={
14+
"name": "New Name",
15+
"email": "[email protected]",
16+
"avatar_url": "https://example.com/avatar.jpg"
17+
},
18+
follow_redirects=False
19+
)
20+
assert response.status_code == 303 # Redirect to login
21+
assert response.headers["location"] == app.url_path_for("read_login")
22+
23+
24+
def test_update_profile_authorized(auth_client: TestClient, test_user: User, session: Session):
25+
"""Test that authorized users can edit their profile"""
26+
27+
# Update profile
28+
response: Response = auth_client.post(
29+
app.url_path_for("update_profile"),
30+
data={
31+
"name": "Updated Name",
32+
"email": "[email protected]",
33+
"avatar_url": "https://example.com/new-avatar.jpg"
34+
},
35+
follow_redirects=False
36+
)
37+
assert response.status_code == 303
38+
assert response.headers["location"] == app.url_path_for("read_profile")
39+
40+
# Verify changes in database
41+
session.refresh(test_user)
42+
assert test_user.name == "Updated Name"
43+
assert test_user.email == "[email protected]"
44+
assert test_user.avatar_url == "https://example.com/new-avatar.jpg"
45+
46+
47+
def test_delete_account_unauthorized(unauth_client: TestClient):
48+
"""Test that unauthorized users cannot delete account"""
49+
response: Response = unauth_client.post(
50+
app.url_path_for("delete_account"),
51+
data={"confirm_delete_password": "Test123!@#"},
52+
follow_redirects=False
53+
)
54+
assert response.status_code == 303 # Redirect to login
55+
assert response.headers["location"] == app.url_path_for("read_login")
56+
57+
58+
def test_delete_account_wrong_password(auth_client: TestClient, test_user: User):
59+
"""Test that account deletion fails with wrong password"""
60+
response: Response = auth_client.post(
61+
app.url_path_for("delete_account"),
62+
data={"confirm_delete_password": "WrongPassword123!"},
63+
follow_redirects=False
64+
)
65+
assert response.status_code == 400
66+
assert "Password is incorrect" in response.text
67+
68+
69+
def test_delete_account_success(auth_client: TestClient, test_user: User, session: Session):
70+
"""Test successful account deletion"""
71+
72+
# Delete account
73+
response: Response = auth_client.post(
74+
app.url_path_for("delete_account"),
75+
data={"confirm_delete_password": "Test123!@#"},
76+
follow_redirects=False
77+
)
78+
assert response.status_code == 303
79+
assert response.headers["location"] == app.url_path_for("logout")
80+
81+
# Verify user is deleted from database
82+
user = session.get(User, test_user.id)
83+
assert user is None

0 commit comments

Comments
 (0)