fix(test): fix task __wrapped__ calls, auth imports, mock chains, async markers, and rate limit mock

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-12 13:03:26 +00:00
parent c297577677
commit 604facf36f
6 changed files with 144 additions and 135 deletions
+12 -7
View File
@@ -66,9 +66,6 @@ def get_gravatar_url(email):
return f"https://www.gravatar.com/avatar/{email_hash}?d=identicon" return f"https://www.gravatar.com/avatar/{email_hash}?d=identicon"
if AUTH_ENABLED:
@router.get("/login")
async def login(request: Request): async def login(request: Request):
"""Show login page with appropriate authentication options""" """Show login page with appropriate authentication options"""
return templates.TemplateResponse( return templates.TemplateResponse(
@@ -83,7 +80,7 @@ if AUTH_ENABLED:
}, },
) )
@router.get("/oauth-login")
async def oauth_login(request: Request): async def oauth_login(request: Request):
"""Handle OAuth login flow""" """Handle OAuth login flow"""
if not OAUTH_CONFIGURED: if not OAUTH_CONFIGURED:
@@ -92,7 +89,7 @@ if AUTH_ENABLED:
redirect_uri = request.url_for("oauth_callback") redirect_uri = request.url_for("oauth_callback")
return await oauth.authentik.authorize_redirect(request, redirect_uri) return await oauth.authentik.authorize_redirect(request, redirect_uri)
@router.get("/oauth-callback")
async def oauth_callback(request: Request): async def oauth_callback(request: Request):
"""Handle OAuth callback from provider""" """Handle OAuth callback from provider"""
try: try:
@@ -137,7 +134,7 @@ if AUTH_ENABLED:
url=f"/login?error=Authentication+failed:+{str(e)}", status_code=status.HTTP_302_FOUND url=f"/login?error=Authentication+failed:+{str(e)}", status_code=status.HTTP_302_FOUND
) )
@router.post("/auth")
async def auth(request: Request): async def auth(request: Request):
"""Handle local username/password authentication""" """Handle local username/password authentication"""
form_data = await request.form() form_data = await request.form()
@@ -160,13 +157,21 @@ if AUTH_ENABLED:
else: else:
return RedirectResponse(url="/login?error=Invalid+username+or+password", status_code=302) return RedirectResponse(url="/login?error=Invalid+username+or+password", status_code=302)
@router.get("/logout")
async def logout(request: Request): async def logout(request: Request):
"""Handle user logout""" """Handle user logout"""
request.session.pop("user", None) request.session.pop("user", None)
return RedirectResponse(url="/login?message=You+have+been+logged+out+successfully", status_code=302) return RedirectResponse(url="/login?message=You+have+been+logged+out+successfully", status_code=302)
if AUTH_ENABLED:
router.add_api_route("/login", login, methods=["GET"])
router.add_api_route("/oauth-login", oauth_login, methods=["GET"])
router.add_api_route("/oauth-callback", oauth_callback, methods=["GET"])
router.add_api_route("/auth", auth, methods=["POST"])
router.add_api_route("/logout", logout, methods=["GET"])
@router.get("/api/auth/whoami") @router.get("/api/auth/whoami")
@require_login @require_login
async def whoami(request: Request): async def whoami(request: Request):
+2 -2
View File
@@ -205,8 +205,8 @@ def oauth_enabled_app(oauth_config: Dict[str, str]):
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from app.main import app from app.main import app
# Create test client # Create test client with base_url to satisfy TrustedHostMiddleware
client = TestClient(app) client = TestClient(app, base_url="http://localhost")
yield client yield client
+21 -22
View File
@@ -163,7 +163,7 @@ class TestRequireLogin:
mock_request.session = {"user": {"id": "123"}} mock_request.session = {"user": {"id": "123"}}
mock_request.url = "http://localhost/test" mock_request.url = "http://localhost/test"
result = test_sync_endpoint(mock_request) result = await test_sync_endpoint(mock_request)
assert result == {"message": "success"} assert result == {"message": "success"}
@@ -180,7 +180,7 @@ class TestRequireLogin:
mock_request.session = {} mock_request.session = {}
mock_request.url = "http://localhost/protected" mock_request.url = "http://localhost/protected"
result = test_sync_endpoint(mock_request) result = await test_sync_endpoint(mock_request)
assert isinstance(result, RedirectResponse) assert isinstance(result, RedirectResponse)
assert result.status_code == status.HTTP_302_FOUND assert result.status_code == status.HTTP_302_FOUND
@@ -192,43 +192,42 @@ class TestOAuthConfiguration:
def test_oauth_not_configured_without_credentials(self): def test_oauth_not_configured_without_credentials(self):
"""Test OAuth is not configured when credentials are missing.""" """Test OAuth is not configured when credentials are missing."""
with patch("app.auth.settings") as mock_settings:
mock_settings.auth_enabled = True
mock_settings.authentik_client_id = None
mock_settings.authentik_client_secret = None
# Re-import to trigger configuration logic
import importlib import importlib
import app.auth import app.auth
try:
with patch("app.config.settings") as mock_settings:
mock_settings.auth_enabled = True
mock_settings.authentik_client_id = None
mock_settings.authentik_client_secret = None
importlib.reload(app.auth) importlib.reload(app.auth)
from app.auth import OAUTH_CONFIGURED assert app.auth.OAUTH_CONFIGURED is False
finally:
assert OAUTH_CONFIGURED is False importlib.reload(app.auth)
def test_oauth_configured_with_credentials(self): def test_oauth_configured_with_credentials(self):
"""Test OAuth is configured when credentials are provided.""" """Test OAuth is configured when credentials are provided."""
with patch("app.auth.settings") as mock_settings: import importlib
with patch("app.auth.oauth") as mock_oauth:
import app.auth
try:
with patch("app.config.settings") as mock_settings:
mock_settings.auth_enabled = True mock_settings.auth_enabled = True
mock_settings.authentik_client_id = "test_client_id" mock_settings.authentik_client_id = "test_client_id"
mock_settings.authentik_client_secret = "test_secret" mock_settings.authentik_client_secret = "test_secret"
mock_settings.authentik_config_url = "https://auth.example.com/.well-known/openid-configuration" mock_settings.authentik_config_url = "https://auth.example.com/.well-known/openid-configuration"
mock_settings.oauth_provider_name = "Test SSO" mock_settings.oauth_provider_name = "Test SSO"
# Re-import to trigger configuration logic
import importlib
import app.auth
importlib.reload(app.auth) importlib.reload(app.auth)
from app.auth import OAUTH_CONFIGURED, OAUTH_PROVIDER_NAME assert app.auth.OAUTH_CONFIGURED is True
assert app.auth.OAUTH_PROVIDER_NAME == "Test SSO"
assert OAUTH_CONFIGURED is True finally:
assert OAUTH_PROVIDER_NAME == "Test SSO" importlib.reload(app.auth)
@pytest.mark.unit @pytest.mark.unit
+8
View File
@@ -64,6 +64,7 @@ class TestOAuthLoginFlow:
class TestOAuthCallback: class TestOAuthCallback:
"""Test OAuth callback handling.""" """Test OAuth callback handling."""
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback_with_valid_token( async def test_oauth_callback_with_valid_token(
self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict
@@ -86,6 +87,7 @@ class TestOAuthCallback:
# Should redirect after successful login # Should redirect after successful login
assert response.status_code == 302 assert response.status_code == 302
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback_stores_user_in_session( async def test_oauth_callback_stores_user_in_session(
self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict
@@ -108,6 +110,7 @@ class TestOAuthCallback:
# Should set session cookie # Should set session cookie
assert "set-cookie" in response.headers or response.status_code == 302 assert "set-cookie" in response.headers or response.status_code == 302
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback_with_admin_user( async def test_oauth_callback_with_admin_user(
self, mock_authorize, oauth_enabled_app: TestClient self, mock_authorize, oauth_enabled_app: TestClient
@@ -131,6 +134,7 @@ class TestOAuthCallback:
# Should successfully authenticate # Should successfully authenticate
assert response.status_code == 302 assert response.status_code == 302
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback_rejects_non_admin( async def test_oauth_callback_rejects_non_admin(
self, mock_authorize, oauth_enabled_app: TestClient self, mock_authorize, oauth_enabled_app: TestClient
@@ -160,6 +164,7 @@ class TestOAuthCallback:
class TestOAuthSessionManagement: class TestOAuthSessionManagement:
"""Test session management with OAuth authentication.""" """Test session management with OAuth authentication."""
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_authenticated_user_can_access_protected_routes( async def test_authenticated_user_can_access_protected_routes(
self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict
@@ -192,6 +197,7 @@ class TestOAuthSessionManagement:
if response.status_code == 302: if response.status_code == 302:
assert "/login" in response.headers.get("location", "") assert "/login" in response.headers.get("location", "")
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_logout_clears_session( async def test_logout_clears_session(
self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict self, mock_authorize, oauth_enabled_app: TestClient, test_user_info: dict
@@ -226,6 +232,7 @@ class TestOAuthErrorHandling:
# Should handle error gracefully # Should handle error gracefully
assert response.status_code in [302, 400] assert response.status_code in [302, 400]
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback_with_invalid_token( async def test_oauth_callback_with_invalid_token(
self, mock_authorize, oauth_enabled_app: TestClient self, mock_authorize, oauth_enabled_app: TestClient
@@ -244,6 +251,7 @@ class TestOAuthErrorHandling:
location = response.headers.get("location", "") location = response.headers.get("location", "")
assert "error" in location.lower() or "login" in location.lower() assert "error" in location.lower() or "login" in location.lower()
@pytest.mark.asyncio
@patch("app.auth.oauth.authentik.authorize_access_token") @patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback_without_userinfo( async def test_oauth_callback_without_userinfo(
self, mock_authorize, oauth_enabled_app: TestClient self, mock_authorize, oauth_enabled_app: TestClient
+1 -1
View File
@@ -136,7 +136,7 @@ class TestRateLimitDecorators:
# Mock limiter to return a simple passthrough decorator # Mock limiter to return a simple passthrough decorator
mock_limiter = MagicMock() mock_limiter = MagicMock()
mock_limiter.exempt.return_value = lambda f: f mock_limiter.exempt.side_effect = lambda f: f
mock_get_limiter.return_value = mock_limiter mock_get_limiter.return_value = mock_limiter
# Decorate function # Decorate function
+16 -19
View File
@@ -44,8 +44,8 @@ class TestStepTimeout:
# Mock database session with proper query chain # Mock database session with proper query chain
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain to return empty list # Set up the query chain to return empty list (single .filter() call with multiple conditions)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.all.return_value = [] mock_db.query.return_value.filter.return_value.all.return_value = []
# Run function # Run function
count = mark_stalled_steps_as_failed(mock_db) count = mark_stalled_steps_as_failed(mock_db)
@@ -74,8 +74,8 @@ class TestStepTimeout:
# Mock database session # Mock database session
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain to return stalled steps # Set up the query chain to return stalled steps (single .filter() call with multiple conditions)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.all.return_value = [step1, step2] mock_db.query.return_value.filter.return_value.all.return_value = [step1, step2]
# Run function # Run function
count = mark_stalled_steps_as_failed(mock_db) count = mark_stalled_steps_as_failed(mock_db)
@@ -105,8 +105,8 @@ class TestStepTimeout:
# Mock database session # Mock database session
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain to return stalled step # Set up the query chain to return stalled step (single .filter() call with multiple conditions)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.all.return_value = [step] mock_db.query.return_value.filter.return_value.all.return_value = [step]
# Run function with 150 second timeout # Run function with 150 second timeout
count = mark_stalled_steps_as_failed(mock_db, timeout_seconds=150) count = mark_stalled_steps_as_failed(mock_db, timeout_seconds=150)
@@ -131,9 +131,8 @@ class TestStepTimeout:
# Mock database session with file filter # Mock database session with file filter
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain with file filter # Set up the query chain with file filter (first .filter() for conditions, second for file_id)
mock_query_chain = mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value mock_db.query.return_value.filter.return_value.filter.return_value.all.return_value = [step]
mock_query_chain.filter.return_value.all.return_value = [step]
# Run function for specific file # Run function for specific file
count = mark_stalled_steps_as_failed(mock_db, file_id=42) count = mark_stalled_steps_as_failed(mock_db, file_id=42)
@@ -158,8 +157,8 @@ class TestStepTimeout:
# Mock database session # Mock database session
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain to return stalled step # Set up the query chain to return stalled step (single .filter() call with multiple conditions)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.all.return_value = [step] mock_db.query.return_value.filter.return_value.all.return_value = [step]
# Run function # Run function
count = mark_stalled_steps_as_failed(mock_db, timeout_seconds=600) count = mark_stalled_steps_as_failed(mock_db, timeout_seconds=600)
@@ -186,8 +185,8 @@ class TestStepTimeout:
# Mock database session # Mock database session
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain to return stalled step # Set up the query chain to return stalled step (single .filter() call with multiple conditions)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.all.return_value = [step] mock_db.query.return_value.filter.return_value.all.return_value = [step]
# Run function # Run function
count = mark_stalled_steps_as_failed(mock_db) count = mark_stalled_steps_as_failed(mock_db)
@@ -212,9 +211,8 @@ class TestStepTimeout:
# Mock database session # Mock database session
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain with file filter # Set up the query chain with file filter (first .filter() for conditions, second for file_id)
mock_query_chain = mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value mock_db.query.return_value.filter.return_value.filter.return_value.all.return_value = [step]
mock_query_chain.filter.return_value.all.return_value = [step]
# Run function # Run function
result = check_and_recover_stalled_file(mock_db, 42) result = check_and_recover_stalled_file(mock_db, 42)
@@ -229,9 +227,8 @@ class TestStepTimeout:
# Mock database session with no stalled steps # Mock database session with no stalled steps
mock_db = MagicMock() mock_db = MagicMock()
# Set up the query chain with file filter # Set up the query chain with file filter (first .filter() for conditions, second for file_id)
mock_query_chain = mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value mock_db.query.return_value.filter.return_value.filter.return_value.all.return_value = []
mock_query_chain.filter.return_value.all.return_value = []
# Run function # Run function
result = check_and_recover_stalled_file(mock_db, 42) result = check_and_recover_stalled_file(mock_db, 42)