Add UUID handling, sorting, filtering, and soft delete functionality to CRUD operations
- Enhanced UUID validation by supporting both string and `UUID` formats. - Added advanced filtering and sorting capabilities to `get_multi_with_total` method. - Introduced soft delete and restore functionality for models with `deleted_at` column. - Updated tests to reflect new endpoints and rate-limiting logic. - Improved schema definitions with `SortParams` and `SortOrder` for consistent API inputs.
This commit is contained in:
@@ -10,6 +10,7 @@ from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.routes.auth import router as auth_router
|
||||
from app.api.routes.users import router as users_router
|
||||
from app.core.auth import get_password_hash
|
||||
from app.core.database import get_db
|
||||
from app.models.user import User
|
||||
@@ -29,6 +30,7 @@ def app(override_get_db):
|
||||
"""Create a FastAPI test application with overridden dependencies."""
|
||||
app = FastAPI()
|
||||
app.include_router(auth_router, prefix="/auth", tags=["auth"])
|
||||
app.include_router(users_router, prefix="/api/v1/users", tags=["users"])
|
||||
|
||||
# Override the get_db dependency
|
||||
app.dependency_overrides[get_db] = lambda: override_get_db
|
||||
@@ -280,9 +282,9 @@ class TestChangePassword:
|
||||
|
||||
# Mock password change to return success
|
||||
with patch.object(AuthService, 'change_password', return_value=True):
|
||||
# Test request
|
||||
response = client.post(
|
||||
"/auth/change-password",
|
||||
# Test request (new endpoint)
|
||||
response = client.patch(
|
||||
"/api/v1/users/me/password",
|
||||
json={
|
||||
"current_password": "OldPassword123",
|
||||
"new_password": "NewPassword123"
|
||||
@@ -291,7 +293,8 @@ class TestChangePassword:
|
||||
|
||||
# Assertions
|
||||
assert response.status_code == 200
|
||||
assert "success" in response.json()["message"].lower()
|
||||
assert response.json()["success"] is True
|
||||
assert "message" in response.json()
|
||||
|
||||
# Clean up override
|
||||
if get_current_user in app.dependency_overrides:
|
||||
@@ -312,18 +315,20 @@ class TestChangePassword:
|
||||
# Mock password change to raise error
|
||||
with patch.object(AuthService, 'change_password',
|
||||
side_effect=AuthenticationError("Current password is incorrect")):
|
||||
# Test request
|
||||
response = client.post(
|
||||
"/auth/change-password",
|
||||
# Test request (new endpoint)
|
||||
response = client.patch(
|
||||
"/api/v1/users/me/password",
|
||||
json={
|
||||
"current_password": "WrongPassword",
|
||||
"new_password": "NewPassword123"
|
||||
}
|
||||
)
|
||||
|
||||
# Assertions
|
||||
assert response.status_code == 400
|
||||
assert "incorrect" in response.json()["detail"].lower()
|
||||
# Assertions - Now returns standardized error response
|
||||
assert response.status_code == 403
|
||||
# The response has standardized error format
|
||||
data = response.json()
|
||||
assert "detail" in data or "errors" in data
|
||||
|
||||
# Clean up override
|
||||
if get_current_user in app.dependency_overrides:
|
||||
|
||||
@@ -13,17 +13,10 @@ from app.core.database import get_db
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""Create a FastAPI test client for the main app with mocked database."""
|
||||
# Mock get_db to avoid connecting to the actual database
|
||||
with patch("app.main.get_db") as mock_get_db:
|
||||
def mock_session_generator():
|
||||
mock_session = MagicMock()
|
||||
# Mock the execute method to return successfully
|
||||
mock_session.execute.return_value = None
|
||||
mock_session.close.return_value = None
|
||||
yield mock_session
|
||||
|
||||
# Return a new generator each time get_db is called
|
||||
mock_get_db.side_effect = lambda: mock_session_generator()
|
||||
# Mock check_database_health to avoid connecting to the actual database
|
||||
with patch("app.main.check_database_health") as mock_health_check:
|
||||
# By default, return True (healthy)
|
||||
mock_health_check.return_value = True
|
||||
yield TestClient(app)
|
||||
|
||||
|
||||
@@ -90,23 +83,14 @@ class TestHealthEndpoint:
|
||||
|
||||
assert data["environment"] == settings.ENVIRONMENT
|
||||
|
||||
def test_health_check_database_connection_failure(self, client):
|
||||
def test_health_check_database_connection_failure(self):
|
||||
"""Test that health check returns unhealthy when database is not accessible"""
|
||||
# Mock the database session to raise an exception
|
||||
with patch("app.main.get_db") as mock_get_db:
|
||||
def mock_session():
|
||||
from unittest.mock import MagicMock
|
||||
mock = MagicMock()
|
||||
mock.execute.side_effect = OperationalError(
|
||||
"Connection refused",
|
||||
params=None,
|
||||
orig=Exception("Connection refused")
|
||||
)
|
||||
yield mock
|
||||
# Mock check_database_health to return False (unhealthy)
|
||||
with patch("app.main.check_database_health") as mock_health_check:
|
||||
mock_health_check.return_value = False
|
||||
|
||||
mock_get_db.return_value = mock_session()
|
||||
|
||||
response = client.get("/health")
|
||||
test_client = TestClient(app)
|
||||
response = test_client.get("/health")
|
||||
|
||||
assert response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
data = response.json()
|
||||
|
||||
@@ -5,6 +5,7 @@ from fastapi.testclient import TestClient
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from app.api.routes.auth import router as auth_router, limiter
|
||||
from app.api.routes.users import router as users_router
|
||||
from app.core.database import get_db
|
||||
|
||||
|
||||
@@ -26,6 +27,7 @@ def app(override_get_db):
|
||||
app.state.limiter = limiter
|
||||
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
||||
app.include_router(auth_router, prefix="/auth", tags=["auth"])
|
||||
app.include_router(users_router, prefix="/api/v1/users", tags=["users"])
|
||||
|
||||
# Override the get_db dependency
|
||||
app.dependency_overrides[get_db] = lambda: override_get_db
|
||||
@@ -159,10 +161,10 @@ class TestChangePasswordRateLimiting:
|
||||
"new_password": "NewPassword123!"
|
||||
}
|
||||
|
||||
# Make 6 requests (limit is 5/minute)
|
||||
# Make 6 requests (limit is 5/minute) - using new endpoint
|
||||
responses = []
|
||||
for i in range(6):
|
||||
response = client.post("/auth/change-password", json=password_data)
|
||||
response = client.patch("/api/v1/users/me/password", json=password_data)
|
||||
responses.append(response)
|
||||
|
||||
# Last request should be rate limited
|
||||
|
||||
Reference in New Issue
Block a user