|
2 | 2 | from fastapi.testclient import TestClient
|
3 | 3 | from sqlmodel import Session, select
|
4 | 4 | from datetime import timedelta
|
| 5 | +from unittest.mock import patch |
| 6 | +import resend |
5 | 7 |
|
6 | 8 | from main import app
|
7 | 9 | from utils.models import User, PasswordResetToken
|
@@ -51,6 +53,24 @@ def test_user_fixture(session: Session):
|
51 | 53 | return user
|
52 | 54 |
|
53 | 55 |
|
| 56 | +# Mock email response fixture |
| 57 | +@pytest.fixture |
| 58 | +def mock_email_response(): |
| 59 | + """ |
| 60 | + Returns a mock Email response object |
| 61 | + """ |
| 62 | + return resend.Email(id="6229f547-f3f6-4eb8-b0dc-82c1b09121b6") |
| 63 | + |
| 64 | + |
| 65 | +@pytest.fixture |
| 66 | +def mock_resend_send(mock_email_response): |
| 67 | + """ |
| 68 | + Patches resend.Emails.send to return a mock response |
| 69 | + """ |
| 70 | + with patch('resend.Emails.send', return_value=mock_email_response) as mock: |
| 71 | + yield mock |
| 72 | + |
| 73 | + |
54 | 74 | # --- Authentication Helper Function Tests ---
|
55 | 75 |
|
56 | 76 |
|
@@ -166,40 +186,56 @@ def test_refresh_token_endpoint(client: TestClient, test_user: User):
|
166 | 186 | assert decoded["sub"] == test_user.email
|
167 | 187 |
|
168 | 188 |
|
169 |
| -# # TODO: Mock email sending |
170 |
| -# def test_password_reset_flow(client: TestClient, session: Session, test_user: User): |
171 |
| -# # Test forgot password request |
172 |
| -# response = client.post( |
173 |
| -# "/auth/forgot_password", |
174 |
| -# data={"email": test_user.email}, |
175 |
| -# follow_redirects=False |
176 |
| -# ) |
177 |
| -# assert response.status_code == 303 |
178 |
| - |
179 |
| -# # Verify reset token was created |
180 |
| -# reset_token = session.exec(select(PasswordResetToken) |
181 |
| -# .where(PasswordResetToken.user_id == test_user.id)).first() |
182 |
| -# assert reset_token is not None |
183 |
| -# assert not reset_token.used |
184 |
| - |
185 |
| -# # Test password reset |
186 |
| -# response = client.post( |
187 |
| -# "/auth/reset_password", |
188 |
| -# data={ |
189 |
| -# "email": test_user.email, |
190 |
| -# "token": reset_token.token, |
191 |
| -# "new_password": "NewPass123!@#", |
192 |
| -# "confirm_new_password": "NewPass123!@#" |
193 |
| -# }, |
194 |
| -# follow_redirects=False |
195 |
| -# ) |
196 |
| -# assert response.status_code == 303 |
197 |
| - |
198 |
| -# # Verify password was updated and token was marked as used |
199 |
| -# session.refresh(test_user) |
200 |
| -# session.refresh(reset_token) |
201 |
| -# assert verify_password("NewPass123!@#", test_user.hashed_password) |
202 |
| -# assert reset_token.used |
| 189 | +def test_password_reset_flow(client: TestClient, session: Session, test_user: User, mock_resend_send): |
| 190 | + # Test forgot password request |
| 191 | + response = client.post( |
| 192 | + "/auth/forgot_password", |
| 193 | + data={"email": test_user.email}, |
| 194 | + follow_redirects=False |
| 195 | + ) |
| 196 | + assert response.status_code == 303 |
| 197 | + |
| 198 | + # Verify the email was "sent" with correct parameters |
| 199 | + mock_resend_send.assert_called_once() |
| 200 | + call_args = mock_resend_send.call_args[0][0] # Get the SendParams argument |
| 201 | + |
| 202 | + # Verify SendParams structure and required fields |
| 203 | + assert isinstance(call_args, dict) |
| 204 | + assert isinstance(call_args["from"], str) |
| 205 | + assert isinstance(call_args["to"], list) |
| 206 | + assert isinstance(call_args["subject"], str) |
| 207 | + assert isinstance(call_args["html"], str) |
| 208 | + |
| 209 | + # Verify content |
| 210 | + assert call_args["to"] == [test_user.email] |
| 211 | + assert call_args[ "from"] == "[email protected]" |
| 212 | + assert "Password Reset Request" in call_args["subject"] |
| 213 | + assert "reset_password" in call_args["html"] |
| 214 | + |
| 215 | + # Verify reset token was created |
| 216 | + reset_token = session.exec(select(PasswordResetToken) |
| 217 | + .where(PasswordResetToken.user_id == test_user.id)).first() |
| 218 | + assert reset_token is not None |
| 219 | + assert not reset_token.used |
| 220 | + |
| 221 | + # Test password reset |
| 222 | + response = client.post( |
| 223 | + "/auth/reset_password", |
| 224 | + data={ |
| 225 | + "email": test_user.email, |
| 226 | + "token": reset_token.token, |
| 227 | + "new_password": "NewPass123!@#", |
| 228 | + "confirm_new_password": "NewPass123!@#" |
| 229 | + }, |
| 230 | + follow_redirects=False |
| 231 | + ) |
| 232 | + assert response.status_code == 303 |
| 233 | + |
| 234 | + # Verify password was updated and token was marked as used |
| 235 | + session.refresh(test_user) |
| 236 | + session.refresh(reset_token) |
| 237 | + assert verify_password("NewPass123!@#", test_user.hashed_password) |
| 238 | + assert reset_token.used |
203 | 239 |
|
204 | 240 |
|
205 | 241 | def test_logout_endpoint(client: TestClient):
|
|
0 commit comments