forked from cardosofelipe/fast-next-template
Add pyproject.toml for consolidated project configuration and replace Black, isort, and Flake8 with Ruff
- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest). - Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting. - Updated `requirements.txt` to include Ruff and remove replaced tools. - Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
"""
|
||||
Tests for auth route exception handlers and error paths.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, AsyncMock
|
||||
from fastapi import status
|
||||
|
||||
|
||||
@@ -11,16 +13,18 @@ class TestLoginSessionCreationFailure:
|
||||
"""Test login when session creation fails."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_succeeds_despite_session_creation_failure(self, client, async_test_user):
|
||||
async def test_login_succeeds_despite_session_creation_failure(
|
||||
self, client, async_test_user
|
||||
):
|
||||
"""Test that login succeeds even if session creation fails."""
|
||||
# Mock session creation to fail
|
||||
with patch('app.api.routes.auth.session_crud.create_session', side_effect=Exception("Session creation failed")):
|
||||
with patch(
|
||||
"app.api.routes.auth.session_crud.create_session",
|
||||
side_effect=Exception("Session creation failed"),
|
||||
):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
json={"email": "testuser@example.com", "password": "TestPassword123!"},
|
||||
)
|
||||
|
||||
# Login should still succeed, just without session record
|
||||
@@ -34,15 +38,20 @@ class TestOAuthLoginSessionCreationFailure:
|
||||
"""Test OAuth login when session creation fails."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_oauth_login_succeeds_despite_session_failure(self, client, async_test_user):
|
||||
async def test_oauth_login_succeeds_despite_session_failure(
|
||||
self, client, async_test_user
|
||||
):
|
||||
"""Test OAuth login succeeds even if session creation fails."""
|
||||
with patch('app.api.routes.auth.session_crud.create_session', side_effect=Exception("Session failed")):
|
||||
with patch(
|
||||
"app.api.routes.auth.session_crud.create_session",
|
||||
side_effect=Exception("Session failed"),
|
||||
):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login/oauth",
|
||||
data={
|
||||
"username": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
"password": "TestPassword123!",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
@@ -54,23 +63,24 @@ class TestRefreshTokenSessionUpdateFailure:
|
||||
"""Test refresh token when session update fails."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_token_succeeds_despite_session_update_failure(self, client, async_test_user):
|
||||
async def test_refresh_token_succeeds_despite_session_update_failure(
|
||||
self, client, async_test_user
|
||||
):
|
||||
"""Test that token refresh succeeds even if session update fails."""
|
||||
# First login to get tokens
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
json={"email": "testuser@example.com", "password": "TestPassword123!"},
|
||||
)
|
||||
tokens = response.json()
|
||||
|
||||
# Mock session update to fail
|
||||
with patch('app.api.routes.auth.session_crud.update_refresh_token', side_effect=Exception("Update failed")):
|
||||
with patch(
|
||||
"app.api.routes.auth.session_crud.update_refresh_token",
|
||||
side_effect=Exception("Update failed"),
|
||||
):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/refresh",
|
||||
json={"refresh_token": tokens["refresh_token"]}
|
||||
"/api/v1/auth/refresh", json={"refresh_token": tokens["refresh_token"]}
|
||||
)
|
||||
|
||||
# Should still succeed - tokens are issued before update
|
||||
@@ -83,15 +93,14 @@ class TestLogoutWithExpiredToken:
|
||||
"""Test logout with expired/invalid token."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logout_with_invalid_token_still_succeeds(self, client, async_test_user):
|
||||
async def test_logout_with_invalid_token_still_succeeds(
|
||||
self, client, async_test_user
|
||||
):
|
||||
"""Test logout succeeds even with invalid refresh token."""
|
||||
# Login first
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
json={"email": "testuser@example.com", "password": "TestPassword123!"},
|
||||
)
|
||||
access_token = response.json()["access_token"]
|
||||
|
||||
@@ -99,7 +108,7 @@ class TestLogoutWithExpiredToken:
|
||||
response = await client.post(
|
||||
"/api/v1/auth/logout",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json={"refresh_token": "invalid.token.here"}
|
||||
json={"refresh_token": "invalid.token.here"},
|
||||
)
|
||||
|
||||
# Should succeed (idempotent)
|
||||
@@ -116,19 +125,16 @@ class TestLogoutWithNonExistentSession:
|
||||
"""Test logout succeeds even if session not found."""
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
json={"email": "testuser@example.com", "password": "TestPassword123!"},
|
||||
)
|
||||
tokens = response.json()
|
||||
|
||||
# Mock session lookup to return None
|
||||
with patch('app.api.routes.auth.session_crud.get_by_jti', return_value=None):
|
||||
with patch("app.api.routes.auth.session_crud.get_by_jti", return_value=None):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/logout",
|
||||
headers={"Authorization": f"Bearer {tokens['access_token']}"},
|
||||
json={"refresh_token": tokens["refresh_token"]}
|
||||
json={"refresh_token": tokens["refresh_token"]},
|
||||
)
|
||||
|
||||
# Should succeed (idempotent)
|
||||
@@ -139,23 +145,25 @@ class TestLogoutUnexpectedError:
|
||||
"""Test logout with unexpected errors."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logout_with_unexpected_error_returns_success(self, client, async_test_user):
|
||||
async def test_logout_with_unexpected_error_returns_success(
|
||||
self, client, async_test_user
|
||||
):
|
||||
"""Test logout returns success even on unexpected errors."""
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
json={"email": "testuser@example.com", "password": "TestPassword123!"},
|
||||
)
|
||||
tokens = response.json()
|
||||
|
||||
# Mock to raise unexpected error
|
||||
with patch('app.api.routes.auth.session_crud.get_by_jti', side_effect=Exception("Unexpected error")):
|
||||
with patch(
|
||||
"app.api.routes.auth.session_crud.get_by_jti",
|
||||
side_effect=Exception("Unexpected error"),
|
||||
):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/logout",
|
||||
headers={"Authorization": f"Bearer {tokens['access_token']}"},
|
||||
json={"refresh_token": tokens["refresh_token"]}
|
||||
json={"refresh_token": tokens["refresh_token"]},
|
||||
)
|
||||
|
||||
# Should still return success (don't expose errors)
|
||||
@@ -172,18 +180,18 @@ class TestLogoutAllUnexpectedError:
|
||||
"""Test logout-all handles database errors."""
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": "testuser@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}
|
||||
json={"email": "testuser@example.com", "password": "TestPassword123!"},
|
||||
)
|
||||
access_token = response.json()["access_token"]
|
||||
|
||||
# Mock to raise database error
|
||||
with patch('app.api.routes.auth.session_crud.deactivate_all_user_sessions', side_effect=Exception("DB error")):
|
||||
with patch(
|
||||
"app.api.routes.auth.session_crud.deactivate_all_user_sessions",
|
||||
side_effect=Exception("DB error"),
|
||||
):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/logout-all",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
@@ -193,7 +201,9 @@ class TestPasswordResetConfirmSessionInvalidation:
|
||||
"""Test password reset invalidates sessions."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_password_reset_continues_despite_session_invalidation_failure(self, client, async_test_user):
|
||||
async def test_password_reset_continues_despite_session_invalidation_failure(
|
||||
self, client, async_test_user
|
||||
):
|
||||
"""Test password reset succeeds even if session invalidation fails."""
|
||||
# Create a valid password reset token
|
||||
from app.utils.security import create_password_reset_token
|
||||
@@ -201,13 +211,13 @@ class TestPasswordResetConfirmSessionInvalidation:
|
||||
token = create_password_reset_token(async_test_user.email)
|
||||
|
||||
# Mock session invalidation to fail
|
||||
with patch('app.api.routes.auth.session_crud.deactivate_all_user_sessions', side_effect=Exception("Invalidation failed")):
|
||||
with patch(
|
||||
"app.api.routes.auth.session_crud.deactivate_all_user_sessions",
|
||||
side_effect=Exception("Invalidation failed"),
|
||||
):
|
||||
response = await client.post(
|
||||
"/api/v1/auth/password-reset/confirm",
|
||||
json={
|
||||
"token": token,
|
||||
"new_password": "NewPassword123!"
|
||||
}
|
||||
json={"token": token, "new_password": "NewPassword123!"},
|
||||
)
|
||||
|
||||
# Should still succeed - password was reset
|
||||
|
||||
Reference in New Issue
Block a user