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:
2025-11-10 11:55:15 +01:00
parent a5c671c133
commit c589b565f0
86 changed files with 4572 additions and 3956 deletions

View File

@@ -2,10 +2,12 @@
"""
Comprehensive tests for async session CRUD operations.
"""
import pytest
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from uuid import uuid4
import pytest
from app.crud.session import session as session_crud
from app.models.user_session import UserSession
from app.schemas.sessions import SessionCreate
@@ -17,7 +19,7 @@ class TestGetByJti:
@pytest.mark.asyncio
async def test_get_by_jti_success(self, async_test_db, async_test_user):
"""Test getting session by JTI."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -27,8 +29,8 @@ class TestGetByJti:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(user_session)
await session.commit()
@@ -41,7 +43,7 @@ class TestGetByJti:
@pytest.mark.asyncio
async def test_get_by_jti_not_found(self, async_test_db):
"""Test getting non-existent JTI returns None."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
result = await session_crud.get_by_jti(session, jti="nonexistent")
@@ -54,7 +56,7 @@ class TestGetActiveByJti:
@pytest.mark.asyncio
async def test_get_active_by_jti_success(self, async_test_db, async_test_user):
"""Test getting active session by JTI."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -64,8 +66,8 @@ class TestGetActiveByJti:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(user_session)
await session.commit()
@@ -78,7 +80,7 @@ class TestGetActiveByJti:
@pytest.mark.asyncio
async def test_get_active_by_jti_inactive(self, async_test_db, async_test_user):
"""Test getting inactive session by JTI returns None."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -88,8 +90,8 @@ class TestGetActiveByJti:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=False,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(user_session)
await session.commit()
@@ -105,7 +107,7 @@ class TestGetUserSessions:
@pytest.mark.asyncio
async def test_get_user_sessions_active_only(self, async_test_db, async_test_user):
"""Test getting only active user sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
active = UserSession(
@@ -115,8 +117,8 @@ class TestGetUserSessions:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
inactive = UserSession(
user_id=async_test_user.id,
@@ -125,17 +127,15 @@ class TestGetUserSessions:
ip_address="192.168.1.2",
user_agent="Mozilla/5.0",
is_active=False,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add_all([active, inactive])
await session.commit()
async with AsyncTestingSessionLocal() as session:
results = await session_crud.get_user_sessions(
session,
user_id=str(async_test_user.id),
active_only=True
session, user_id=str(async_test_user.id), active_only=True
)
assert len(results) == 1
assert results[0].is_active is True
@@ -143,7 +143,7 @@ class TestGetUserSessions:
@pytest.mark.asyncio
async def test_get_user_sessions_all(self, async_test_db, async_test_user):
"""Test getting all user sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
for i in range(3):
@@ -154,17 +154,15 @@ class TestGetUserSessions:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=i % 2 == 0,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(sess)
await session.commit()
async with AsyncTestingSessionLocal() as session:
results = await session_crud.get_user_sessions(
session,
user_id=str(async_test_user.id),
active_only=False
session, user_id=str(async_test_user.id), active_only=False
)
assert len(results) == 3
@@ -175,7 +173,7 @@ class TestCreateSession:
@pytest.mark.asyncio
async def test_create_session_success(self, async_test_db, async_test_user):
"""Test successfully creating a session_crud."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
session_data = SessionCreate(
@@ -185,10 +183,10 @@ class TestCreateSession:
device_id="device_123",
ip_address="192.168.1.100",
user_agent="Mozilla/5.0",
last_used_at=datetime.now(timezone.utc),
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(UTC),
expires_at=datetime.now(UTC) + timedelta(days=7),
location_city="San Francisco",
location_country="USA"
location_country="USA",
)
result = await session_crud.create_session(session, obj_in=session_data)
@@ -204,7 +202,7 @@ class TestDeactivate:
@pytest.mark.asyncio
async def test_deactivate_success(self, async_test_db, async_test_user):
"""Test successfully deactivating a session_crud."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -214,8 +212,8 @@ class TestDeactivate:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(user_session)
await session.commit()
@@ -229,7 +227,7 @@ class TestDeactivate:
@pytest.mark.asyncio
async def test_deactivate_not_found(self, async_test_db):
"""Test deactivating non-existent session returns None."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
result = await session_crud.deactivate(session, session_id=str(uuid4()))
@@ -240,9 +238,11 @@ class TestDeactivateAllUserSessions:
"""Tests for deactivate_all_user_sessions method."""
@pytest.mark.asyncio
async def test_deactivate_all_user_sessions_success(self, async_test_db, async_test_user):
async def test_deactivate_all_user_sessions_success(
self, async_test_db, async_test_user
):
"""Test deactivating all user sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
# Create minimal sessions for test (2 instead of 5)
@@ -254,16 +254,15 @@ class TestDeactivateAllUserSessions:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(sess)
await session.commit()
async with AsyncTestingSessionLocal() as session:
count = await session_crud.deactivate_all_user_sessions(
session,
user_id=str(async_test_user.id)
session, user_id=str(async_test_user.id)
)
assert count == 2
@@ -274,7 +273,7 @@ class TestUpdateLastUsed:
@pytest.mark.asyncio
async def test_update_last_used_success(self, async_test_db, async_test_user):
"""Test updating last_used_at timestamp."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -284,8 +283,8 @@ class TestUpdateLastUsed:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc) - timedelta(hours=1)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC) - timedelta(hours=1),
)
session.add(user_session)
await session.commit()
@@ -303,7 +302,7 @@ class TestGetUserSessionCount:
@pytest.mark.asyncio
async def test_get_user_session_count_success(self, async_test_db, async_test_user):
"""Test getting user session count."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
for i in range(3):
@@ -314,28 +313,26 @@ class TestGetUserSessionCount:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(sess)
await session.commit()
async with AsyncTestingSessionLocal() as session:
count = await session_crud.get_user_session_count(
session,
user_id=str(async_test_user.id)
session, user_id=str(async_test_user.id)
)
assert count == 3
@pytest.mark.asyncio
async def test_get_user_session_count_empty(self, async_test_db):
"""Test getting session count for user with no sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
count = await session_crud.get_user_session_count(
session,
user_id=str(uuid4())
session, user_id=str(uuid4())
)
assert count == 0
@@ -346,7 +343,7 @@ class TestUpdateRefreshToken:
@pytest.mark.asyncio
async def test_update_refresh_token_success(self, async_test_db, async_test_user):
"""Test updating refresh token JTI and expiration."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -356,26 +353,34 @@ class TestUpdateRefreshToken:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc) - timedelta(hours=1)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC) - timedelta(hours=1),
)
session.add(user_session)
await session.commit()
await session.refresh(user_session)
new_jti = "new_jti_123"
new_expires = datetime.now(timezone.utc) + timedelta(days=14)
new_expires = datetime.now(UTC) + timedelta(days=14)
result = await session_crud.update_refresh_token(
session,
session=user_session,
new_jti=new_jti,
new_expires_at=new_expires
new_expires_at=new_expires,
)
assert result.refresh_token_jti == new_jti
# Compare timestamps ignoring timezone info
assert abs((result.expires_at.replace(tzinfo=None) - new_expires.replace(tzinfo=None)).total_seconds()) < 1
assert (
abs(
(
result.expires_at.replace(tzinfo=None)
- new_expires.replace(tzinfo=None)
).total_seconds()
)
< 1
)
class TestCleanupExpired:
@@ -384,7 +389,7 @@ class TestCleanupExpired:
@pytest.mark.asyncio
async def test_cleanup_expired_success(self, async_test_db, async_test_user):
"""Test cleaning up old expired inactive sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create old expired inactive session
async with AsyncTestingSessionLocal() as session:
@@ -395,9 +400,9 @@ class TestCleanupExpired:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=False,
expires_at=datetime.now(timezone.utc) - timedelta(days=5),
last_used_at=datetime.now(timezone.utc) - timedelta(days=35),
created_at=datetime.now(timezone.utc) - timedelta(days=35)
expires_at=datetime.now(UTC) - timedelta(days=5),
last_used_at=datetime.now(UTC) - timedelta(days=35),
created_at=datetime.now(UTC) - timedelta(days=35),
)
session.add(old_session)
await session.commit()
@@ -410,7 +415,7 @@ class TestCleanupExpired:
@pytest.mark.asyncio
async def test_cleanup_expired_keeps_recent(self, async_test_db, async_test_user):
"""Test that cleanup keeps recent expired sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create recent expired inactive session (less than keep_days old)
async with AsyncTestingSessionLocal() as session:
@@ -421,9 +426,9 @@ class TestCleanupExpired:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=False,
expires_at=datetime.now(timezone.utc) - timedelta(hours=1),
last_used_at=datetime.now(timezone.utc) - timedelta(hours=2),
created_at=datetime.now(timezone.utc) - timedelta(days=1)
expires_at=datetime.now(UTC) - timedelta(hours=1),
last_used_at=datetime.now(UTC) - timedelta(hours=2),
created_at=datetime.now(UTC) - timedelta(days=1),
)
session.add(recent_session)
await session.commit()
@@ -436,7 +441,7 @@ class TestCleanupExpired:
@pytest.mark.asyncio
async def test_cleanup_expired_keeps_active(self, async_test_db, async_test_user):
"""Test that cleanup does not delete active sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create old expired but ACTIVE session
async with AsyncTestingSessionLocal() as session:
@@ -447,9 +452,9 @@ class TestCleanupExpired:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True, # Active
expires_at=datetime.now(timezone.utc) - timedelta(days=5),
last_used_at=datetime.now(timezone.utc) - timedelta(days=35),
created_at=datetime.now(timezone.utc) - timedelta(days=35)
expires_at=datetime.now(UTC) - timedelta(days=5),
last_used_at=datetime.now(UTC) - timedelta(days=35),
created_at=datetime.now(UTC) - timedelta(days=35),
)
session.add(active_session)
await session.commit()
@@ -464,9 +469,11 @@ class TestCleanupExpiredForUser:
"""Tests for cleanup_expired_for_user method."""
@pytest.mark.asyncio
async def test_cleanup_expired_for_user_success(self, async_test_db, async_test_user):
async def test_cleanup_expired_for_user_success(
self, async_test_db, async_test_user
):
"""Test cleaning up expired sessions for specific user."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create expired inactive session for user
async with AsyncTestingSessionLocal() as session:
@@ -477,8 +484,8 @@ class TestCleanupExpiredForUser:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=False,
expires_at=datetime.now(timezone.utc) - timedelta(days=1),
last_used_at=datetime.now(timezone.utc) - timedelta(days=2)
expires_at=datetime.now(UTC) - timedelta(days=1),
last_used_at=datetime.now(UTC) - timedelta(days=2),
)
session.add(expired_session)
await session.commit()
@@ -486,27 +493,27 @@ class TestCleanupExpiredForUser:
# Cleanup for user
async with AsyncTestingSessionLocal() as session:
count = await session_crud.cleanup_expired_for_user(
session,
user_id=str(async_test_user.id)
session, user_id=str(async_test_user.id)
)
assert count == 1
@pytest.mark.asyncio
async def test_cleanup_expired_for_user_invalid_uuid(self, async_test_db):
"""Test cleanup with invalid user UUID."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
with pytest.raises(ValueError, match="Invalid user ID format"):
await session_crud.cleanup_expired_for_user(
session,
user_id="not-a-valid-uuid"
session, user_id="not-a-valid-uuid"
)
@pytest.mark.asyncio
async def test_cleanup_expired_for_user_keeps_active(self, async_test_db, async_test_user):
async def test_cleanup_expired_for_user_keeps_active(
self, async_test_db, async_test_user
):
"""Test that cleanup for user keeps active sessions."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create expired but active session
async with AsyncTestingSessionLocal() as session:
@@ -517,8 +524,8 @@ class TestCleanupExpiredForUser:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True, # Active
expires_at=datetime.now(timezone.utc) - timedelta(days=1),
last_used_at=datetime.now(timezone.utc) - timedelta(days=2)
expires_at=datetime.now(UTC) - timedelta(days=1),
last_used_at=datetime.now(UTC) - timedelta(days=2),
)
session.add(active_session)
await session.commit()
@@ -526,8 +533,7 @@ class TestCleanupExpiredForUser:
# Cleanup
async with AsyncTestingSessionLocal() as session:
count = await session_crud.cleanup_expired_for_user(
session,
user_id=str(async_test_user.id)
session, user_id=str(async_test_user.id)
)
assert count == 0 # Should not delete active sessions
@@ -536,9 +542,11 @@ class TestGetUserSessionsWithUser:
"""Tests for get_user_sessions with eager loading."""
@pytest.mark.asyncio
async def test_get_user_sessions_with_user_relationship(self, async_test_db, async_test_user):
async def test_get_user_sessions_with_user_relationship(
self, async_test_db, async_test_user
):
"""Test getting sessions with user relationship loaded."""
test_engine, AsyncTestingSessionLocal = async_test_db
_test_engine, AsyncTestingSessionLocal = async_test_db
async with AsyncTestingSessionLocal() as session:
user_session = UserSession(
@@ -548,8 +556,8 @@ class TestGetUserSessionsWithUser:
ip_address="192.168.1.1",
user_agent="Mozilla/5.0",
is_active=True,
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
last_used_at=datetime.now(timezone.utc)
expires_at=datetime.now(UTC) + timedelta(days=7),
last_used_at=datetime.now(UTC),
)
session.add(user_session)
await session.commit()
@@ -557,8 +565,6 @@ class TestGetUserSessionsWithUser:
# Get with user relationship
async with AsyncTestingSessionLocal() as session:
results = await session_crud.get_user_sessions(
session,
user_id=str(async_test_user.id),
with_user=True
session, user_id=str(async_test_user.id), with_user=True
)
assert len(results) >= 1